lib/remedy/gateway/wsstate.ex

defmodule Remedy.Gateway.WSState do
  @moduledoc """
  Contains all the information required to maintain the gateway websocket connection to Discord.

  This is provided to allow the user to enact custom logic on the gateway events as they are received from the consumer. It should be noted that the websocket state consumed with an event is a 'snapshot' of the state at the time that event was received, and does not relate to the current state of the websocket.
  """
  use Remedy.Schema

  @type token :: String.t()
  @type shard :: integer()
  @type gateway :: String.t()
  @type session_id :: String.t() | nil
  @type shard_pid :: pid()
  @type heartbeat_interval :: integer()
  @type conn :: pid()
  @type gun_conn :: pid()
  @type gun_data_stream :: reference()
  @type zlib_context :: term()
  @type last_heartbeat_send :: DateTime.t() | nil
  @type last_heartbeat_ack :: DateTime.t() | nil
  @type heartbeat_ack :: boolean()
  @type heartbeat_timer :: integer()
  @type payload_op_code :: integer()
  @type payload_op_event :: atom()
  @type payload_sequence :: integer()
  @type payload_dispatch_event :: atom()

  @type t :: %__MODULE__{
          token: token,
          shard: shard,
          gateway: gateway,
          session_id: session_id,
          heartbeat_interval: heartbeat_interval,
          conn: conn,
          gun_conn: gun_conn,
          gun_data_stream: gun_data_stream,
          zlib_context: zlib_context,
          last_heartbeat_send: last_heartbeat_send,
          last_heartbeat_ack: last_heartbeat_ack,
          heartbeat_ack: heartbeat_ack,
          heartbeat_timer: heartbeat_timer,
          payload_op_code: payload_op_code,
          payload_op_event: payload_op_event,
          payload_sequence: payload_sequence,
          payload_dispatch_event: payload_dispatch_event
        }

  @primary_key false
  embedded_schema do
    # We know all this when connecting
    field :token, :string, default: Application.get_env(:remedy, :token)
    field :shard, :integer
    field :gateway, :string
    field :v, :integer
    field :session_id, :string
    # Need to store to maintain connection
    field :shard_pid, :any, virtual: true, default: self()
    field :heartbeat_interval, :integer
    # Heartbeat
    field :last_heartbeat_send, :utc_datetime
    field :last_heartbeat_ack, :utc_datetime
    field :heartbeat_ack, :boolean
    field :heartbeat_timer, :any, virtual: true
    # Gun INfo
    field :conn, :any, virtual: true
    field :gun_conn, :any, virtual: true
    field :gun_data_stream, :any, virtual: true
    field :zlib_context, :any, virtual: true
    # Payload items that can actually be used.
    field :payload_op_code, :integer, default: 0
    field :payload_op_event, :string, default: ""
    field :payload_sequence, :integer
    field :payload_dispatch_event, :any, virtual: true
  end

  @doc """
  Gets the latency of the shard connection from a `Remedy.Struct.WSState.t()` struct.

  Returns the latency in milliseconds as an integer, returning nil if unknown.
  """
  def get_shard_latency(%__MODULE__{last_heartbeat_ack: nil}), do: nil

  def get_shard_latency(%__MODULE__{last_heartbeat_send: nil}), do: nil

  def get_shard_latency(
        %__MODULE__{
          last_heartbeat_ack: last_heartbeat_ack,
          last_heartbeat_send: last_heartbeat_send
        } = state
      ) do
    latency = DateTime.diff(last_heartbeat_ack, last_heartbeat_send, :millisecond)

    max(0, latency + if(latency < 0, do: state.heartbeat_interval, else: 0))
  end
end