Class: SlackSocketModeBot::SimpleWebSocket

Inherits:
Object
  • Object
show all
Defined in:
lib/slack_socket_mode_bot/simple_web_socket.rb

Constant Summary collapse

CLOSE_TIMEOUT =

Seconds to wait for the server's TCP close before dropping the socket ourselves.

10

Instance Method Summary collapse

Constructor Details

#initialize(url) ⇒ SimpleWebSocket

Returns a new instance of SimpleWebSocket.



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/slack_socket_mode_bot/simple_web_socket.rb', line 5

def initialize(url)
  uri = URI.parse(url)

  unless uri.scheme == "https" || uri.scheme == "wss"
    raise "unexpected scheme (not secure?): #{ uri.scheme }"
  end

  ctx = OpenSSL::SSL::SSLContext.new
  ctx.ssl_version = "SSLv23"
  ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
  ctx.cert_store = OpenSSL::X509::Store.new
  ctx.cert_store.set_default_paths

  count = 0
  begin
    io = TCPSocket.new(uri.host, uri.port || 443)
    @io = OpenSSL::SSL::SSLSocket.new(io, ctx)
    @io.connect
  rescue Socket::ResolutionError
    sleep 1
    count += 1
    retry if count < 3
    raise
  end

  @version = nil
  @close_deadline = nil # nil while open; a Time once closing

  @fib = Fiber.new do
    begin
      handshake = WebSocket::Handshake::Client.new(url: url)
      @write_buff = handshake.to_s.dup
      handshake << Fiber.yield until handshake.finished?

      @version = handshake.version
      yield :open

      frame = WebSocket::Frame::Incoming::Client.new
      frame << handshake.leftovers
      while true
        while msg = frame.next
          case msg.type
          when :close
            was_closing = closing?
            close
            yield :close unless was_closing
          when :ping
            send(msg.data, type: :pong)
          when :pong
          when :text
            yield :message, msg.data, :text
          when :binary
            yield :message, msg.data, :binary
          end
        end
        frame << Fiber.yield
      end
    rescue EOFError
    ensure
      yield :close unless closing?
    end
  end

  @fib.resume
end

Instance Method Details

#close(code: 1000, reason: "") ⇒ Object



79
80
81
82
83
# File 'lib/slack_socket_mode_bot/simple_web_socket.rb', line 79

def close(code: 1000, reason: "")
  return if closing? || @io.closed?
  send(reason, type: :close, code: code)
  @close_deadline = Time.now + CLOSE_TIMEOUT
end

#closing?Boolean

Returns:

  • (Boolean)


71
# File 'lib/slack_socket_mode_bot/simple_web_socket.rb', line 71

def closing? = !@close_deadline.nil?

#send(data, type: :text, code: nil) ⇒ Object



73
74
75
76
77
# File 'lib/slack_socket_mode_bot/simple_web_socket.rb', line 73

def send(data, type: :text, code: nil)
  raise "not opened yet" unless @version
  frame = WebSocket::Frame::Outgoing::Client.new(version: @version, data: data, type: type, code: code)
  @write_buff << frame.to_s
end

#step(read_ios, write_ios) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/slack_socket_mode_bot/simple_web_socket.rb', line 85

def step(read_ios, write_ios)
  if @close_deadline && Time.now >= @close_deadline
    @io.close unless @io.closed?
    return false
  end

  wait_readable = wait_writable = false

  while true
    read_buff = @io.read_nonblock(4096, exception: false)
    case read_buff
    when :wait_readable then wait_readable = true; break
    when :wait_writable then wait_writable = true; break
    when nil
      raise Errno::EPIPE
    else
      @fib.resume(read_buff)
    end
  end

  unless @write_buff.empty?
    len = @io.write_nonblock(@write_buff, exception: false)
    case len
    when :wait_readable then wait_readable = true
    when :wait_writable then wait_writable = true
    else
      @write_buff.clear
    end
  end

  read_ios << @io if wait_readable
  write_ios << @io if wait_writable
  return true

rescue Errno::EPIPE, Errno::ECONNRESET
  begin
    @fib.raise(EOFError)
  rescue FiberError
  end
  @io.close unless @io.closed?
  false
end