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.



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

def initialize(url, verify_ssl: true)
  @uri        = URI.parse(url)
  @verify_ssl = verify_ssl
  @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



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

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



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

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

  @socket = if @uri.scheme == "wss"
    ctx = OpenSSL::SSL::SSLContext.new
    ctx.verify_mode = @verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
    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.



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

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