Class: Wokku::CableClient

Inherits:
Object
  • Object
show all
Defined in:
lib/wokku/cable_client.rb

Constant Summary collapse

SUBSCRIBE_TIMEOUT =
10

Instance Method Summary collapse

Constructor Details

#initialize(url:, token:) ⇒ CableClient

Returns a new instance of CableClient.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/wokku/cable_client.rb', line 13

def initialize(url:, token:)
  @uri = URI(url)
  @token = token
  @on_message = ->(_) {}
  @on_close = ->(_) {}
  @subscribed = false
  @identifier = nil
  @socket = open_socket
  @driver = WebSocket::Driver.client(self)
  @driver.set_header("Authorization", "Bearer #{@token}") if @token && !@token.empty?
  @driver.set_header("Origin", origin_header)
  attach_driver_callbacks
  @driver.start
end

Instance Method Details

#closeObject



76
77
78
79
# File 'lib/wokku/cable_client.rb', line 76

def close
  @driver.close rescue nil
  @socket.close rescue nil
end

#on_close(&block) ⇒ Object



50
51
52
# File 'lib/wokku/cable_client.rb', line 50

def on_close(&block)
  @on_close = block
end

#on_message(&block) ⇒ Object



46
47
48
# File 'lib/wokku/cable_client.rb', line 46

def on_message(&block)
  @on_message = block
end

#origin_headerObject

Origin matches the cable host so ActionCable’s same-origin check passes. CSRF concerns don’t apply: the CLI authenticates via Bearer token, not a cookie-borne session, so cross-site forgery is not a vector here.



31
32
33
34
35
# File 'lib/wokku/cable_client.rb', line 31

def origin_header
  scheme = @uri.scheme == "wss" ? "https" : "http"
  port = (@uri.port && ![ 80, 443 ].include?(@uri.port)) ? ":#{@uri.port}" : ""
  "#{scheme}://#{@uri.host}#{port}"
end

#pump(timeout = 0.05) ⇒ Object



66
67
68
69
70
71
72
73
74
# File 'lib/wokku/cable_client.rb', line 66

def pump(timeout = 0.05)
  ready = IO.select([@socket], nil, nil, timeout)
  return unless ready
  @driver.parse(@socket.read_nonblock(4096))
rescue IO::WaitReadable
  # transient
rescue EOFError
  @on_close.call(:eof)
end

#send_message(payload) ⇒ Object



62
63
64
# File 'lib/wokku/cable_client.rb', line 62

def send_message(payload)
  @driver.text(JSON.dump(command: "message", identifier: @identifier, data: JSON.dump(payload)))
end

#subscribe(channel:, params: {}) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/wokku/cable_client.rb', line 54

def subscribe(channel:, params: {})
  @identifier = JSON.dump({ channel: channel, **params })
  @driver.text(JSON.dump(command: "subscribe", identifier: @identifier))
  deadline = Time.now + SUBSCRIBE_TIMEOUT
  pump_until { @subscribed || Time.now > deadline }
  raise "subscribe timed out" unless @subscribed
end

#urlObject

WebSocket::Driver duck-types these on its socket adapter:



38
39
40
# File 'lib/wokku/cable_client.rb', line 38

def url
  @uri.to_s
end

#write(data) ⇒ Object



42
43
44
# File 'lib/wokku/cable_client.rb', line 42

def write(data)
  @socket.write(data)
end