class Forward::Socket

Constants

ACTIVITY_TIMEOUT
HEART_BEAT_INTERVAL
WATCH_INTERVAL

Public Class Methods

new(tunnel) click to toggle source
# File lib/forward/socket.rb, line 8
def initialize(tunnel)
  @tunnel = tunnel
  @socket = Faye::WebSocket::Client.new(@tunnel.tunneler)
  @socket.on :open do |event|
    logger.debug '[socket] open'
    send(type: 'tunnel:identify', tunnelId: @tunnel.id)
  end

  @socket.on :message do |event|
    receive(event.data)
  end

  @socket.on :close do |event|
    logger.debug "[socket] close - code: #{event.code} reason: #{event.reason}"
    if @tunnel.open?
      exit_with_message "Your tunnel has been disconnected"
    else
      @tunnel.destroy { exit_with_error "Unable to open tunnel, please contact us at #{SUPPORT_EMAIL}" }
    end
  end

  @socket.on :error do |event|
    logger.debug "[socket] error #{event.inspect}"
  end
end

Public Instance Methods

send(message, data = nil) click to toggle source
# File lib/forward/socket.rb, line 34
def send(message, data = nil)
  @socket.send(pack(message, data))
end

Private Instance Methods

beat() click to toggle source
# File lib/forward/socket.rb, line 86
def beat
  EM.add_periodic_timer(HEART_BEAT_INTERVAL) do
    logger.debug "[heartbeat] lub"
    send(type: 'heartbeat:lub')
  end
end
pack(message, data = nil) click to toggle source
# File lib/forward/socket.rb, line 105
def pack(message, data = nil)
  message = message.is_a?(String) ? message : message.to_json
  message.encode!('utf-16le')

  bytes = Array(message.bytesize).pack('v').bytes.to_a
  bytes += message.bytes.to_a
  bytes += data.bytes.to_a unless data.nil?

  bytes
end
receive(data) click to toggle source
# File lib/forward/socket.rb, line 40
def receive(data)
  message, data = unpack(data)
  logger.debug "[socket] #{message[:type]}"

  @tunnel.track_activity!

  if message[:type] == 'tunnel:ready'
    @tunnel.ready!
    beat
    watch

    return
  end

  if message[:type] == 'tunnel:notfound'
    logger.debug "[socket] tunnel not found"
    exit_with_message "Unable to open tunnel, please contact us at #{SUPPORT_EMAIL}"
  end

  if message[:type] == 'notification'
    puts message[:message]

    return
  end

  if message[:type] == 'request:start'
    logger.debug "[request] start #{message[:url]}"
    request = Request.new(@tunnel, message)
    @tunnel.requests[request.id] = request
    return
  end

  request = @tunnel.requests[message[:id]]
  return if request.nil?

  if message[:type] == 'request:data'
    logger.debug '[request] data'
    request << data
  end

  if message[:type] == 'request:end'
    logger.debug '[request] end'
    request.process
  end
end
unpack(bytes) click to toggle source
# File lib/forward/socket.rb, line 116
def unpack(bytes)
  bytes         = bytes.pack('C*')
  message_size  = bytes.unpack('v')[0]
  message       = bytes.byteslice(2, message_size).force_encoding('utf-16le')
  binary_offset = 2 + message_size

  [JSON.parse(message, symbolize_names: true), bytes.byteslice(binary_offset..-1)]
end
watch() click to toggle source
# File lib/forward/socket.rb, line 93
def watch
  EM.add_periodic_timer(WATCH_INTERVAL) do
    logger.debug "[socket] checking activity"
    inactive_for = Time.now.to_i - @tunnel.last_active_at

    if inactive_for > ACTIVITY_TIMEOUT
      logger.debug "[socket] closing due to inactivity (heartbeats and requests)"
      @socket.close
    end
  end
end