Class: Melaya::MelayaWebSocket

Inherits:
Object
  • Object
show all
Defined in:
lib/melaya/stream.rb

Overview

A minimal RFC 6455 WebSocket client built on stdlib TCP+TLS. No external gem required. Supports text frames; yields parsed JSON frames.

Usage (block form — closes automatically):

MelayaWebSocket.connect(url, verify_ssl: true) do |ws|
ws.each_frame { |frame| puts frame.inspect; break }
end

Usage (manual):

ws = MelayaWebSocket.new(url, verify_ssl: true)
ws.connect
ws.each_frame { |f| ... }
ws.close

Constant Summary collapse

GUID =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url, verify_ssl: true) ⇒ MelayaWebSocket

Returns a new instance of MelayaWebSocket.

Raises:

  • (ArgumentError)


44
45
46
47
48
49
50
51
# File 'lib/melaya/stream.rb', line 44

def initialize(url, verify_ssl: true)
  raise ArgumentError, "Melaya: TLS certificate verification cannot be disabled." unless verify_ssl
  @uri        = URI.parse(url)
  @verify_ssl = true
  @socket     = nil
  @closed     = false
  @buf        = String.new("", encoding: "BINARY")
end

Class Method Details

.connect(url, verify_ssl: true, &block) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/melaya/stream.rb', line 30

def self.connect(url, verify_ssl: true, &block)
  ws = new(url, verify_ssl: verify_ssl)
  ws.connect
  if block_given?
    begin
      block.call(ws)
    ensure
      ws.close
    end
  else
    ws
  end
end

Instance Method Details

#closeObject



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/melaya/stream.rb', line 92

def close
  return if @closed
  @closed = true
  begin
    # Send a close frame (opcode 0x8) with no body, masked
    send_frame(0x8, "")
  rescue StandardError
    # best-effort
  ensure
    @socket&.close rescue nil
  end
end

#connectObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/melaya/stream.rb', line 53

def connect
  tcp = TCPSocket.new(@uri.host, @uri.port)

  @socket = if @uri.scheme == "wss"
    ctx = OpenSSL::SSL::SSLContext.new
    ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
    ssl = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
    ssl.hostname = @uri.host
    ssl.connect
    ssl
  else
    tcp
  end

  handshake
  self
end

#each_frameObject

Iterate over incoming JSON frames. Yields each parsed frame Hash. Blocks until the socket closes or the block calls break.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/melaya/stream.rb', line 73

def each_frame
  loop do
    frame = read_frame
    break if frame.nil? # connection closed

    next if frame.empty? # ping/pong or non-text

    begin
      yield JSON.parse(frame)
    rescue JSON::ParserError
      next # ignore non-JSON keep-alive text
    end
  end
rescue IOError, Errno::ECONNRESET, EOFError
  # socket closed by remote
ensure
  close
end