absinthe-graphql / absinthe-socket

Core JavaScript support for Absinthe WS-based operations
MIT License
150 stars 75 forks source link

feat: Added optional inactivity timeout #40

Closed fabian-ahorner closed 5 years ago

fabian-ahorner commented 5 years ago

This is inspired by the inactivityTimeout in https://github.com/apollographql/subscriptions-transport-ws. It will automatically close socket connection after a timeout if no subscriptions are active.

Given that there is no options passed in at the moment when creating a new absintheSocket, I was not sure if this is the best way of doing it. Please let me know if you want me to change this or move the implementation somewhere else.

benwilson512 commented 5 years ago

Hey @wayrunner can you elaborate on why this is important to do? Holding open connections doesn't consume any resources within your application and there's already a heartbeat established in the underlying transport.

fabian-ahorner commented 5 years ago

Hey, we are running the application on Heroku and are worried about the connection limit on their platform. We don't need subscriptions while the web app is in the foreground and rather close the socket to keep the number of connections low.

tlvenn commented 5 years ago

Hi @wayrunner,

Is there any reason you don't want to simply manage this concern within your app directly given you have access to the socket and you can simply choose to close it whenever you want ?

I would rather keep the core as lean as possible and use an HOC to deal with inactivity if that's needed / desirable.

fabian-ahorner commented 5 years ago

@tlvenn I was having issues reopening the connection at a later stage. I found a solution using ApolloLink though that seems to be working well. Sorry for the pointless pull request 😅

import { ApolloLink } from 'apollo-link'
import Observable from 'zen-observable'

/**
 * @example
 * new InactivityWebSocketLink(socket, 5000, () => createAbsintheSocketLink(AbsintheSocket.create(socket)))
 */
export default class InactivityWebSocketLink extends ApolloLink {
  activeConnections = 0

  constructor (socket, inactivityTimeout, createLink) {
    super()
    this.socket = socket
    this.timeout = null
    this.inactivityTimeout = inactivityTimeout
    this.createLink = createLink
    this.link = createLink()
  }

  disconnect = () => {
    this.socket.disconnect()
    this.link = this.createLink()
  }

  request (operation, forward) {
    this.activeConnections++
    if (this.timeout) {
      clearTimeout(this.timeout)
    }
    const next = this.link.request(operation, forward)
    return new Observable(observer => {
      const subscription = next.subscribe({
        complete: observer.complete.bind(observer),
        next: observer.next.bind(observer),
        error: observer.complete.bind(observer)
      })

      return () => {
        this.activeConnections--
        if (this.activeConnections === 0) {
          this.timeout = setTimeout(this.disconnect, this.inactivityTimeout)
        }
        if (subscription) subscription.unsubscribe()
      }
    })
  }
}