Class: Smplkit::SharedWebSocket

Inherits:
Object
  • Object
show all
Defined in:
lib/smplkit/ws.rb

Overview

Manages a single WebSocket connection to the app service event gateway.

A single SharedWebSocket instance is shared across all product modules (config, flags) within one Smplkit::Client. Product modules register listeners for specific event types; the shared connection dispatches incoming events to the appropriate listeners.

The connection runs on a dedicated SDK-owned thread; public methods are thread-safe and non-blocking.

The app service gateway protocol:

- Connect to +wss://app.<base_domain>/api/ws/v1/events?api_key={key}+
- Receive +{"type": "connected"}+ on success
- Receive events: +{"event": "config_changed", ...}+, etc.
- No subscribe message - the API key determines the account
- Heartbeat: server sends +"ping"+ (text), client responds with +"pong"+

NOTE: The actual WebSocket I/O is wired to async-websocket on a worker thread. The initial Ruby SDK release defers full live-update wiring to a follow-up because async-websocket interactions need integration testing against the real platform.

Constant Summary collapse

BACKOFF_SCHEDULE =
[1, 2, 4, 8, 16, 32, 60].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app_base_url:, api_key:, metrics: nil) ⇒ SharedWebSocket

Returns a new instance of SharedWebSocket.



30
31
32
33
34
35
36
37
38
# File 'lib/smplkit/ws.rb', line 30

def initialize(app_base_url:, api_key:, metrics: nil)
  @app_base_url = app_base_url
  @api_key = api_key
  @metrics = metrics
  @listeners = Concurrent::Hash.new { |h, k| h[k] = [] }
  @listeners_lock = Mutex.new
  @connection_status = "disconnected"
  @closed = false
end

Instance Attribute Details

#connection_statusObject (readonly)

Returns the value of attribute connection_status.



57
58
59
# File 'lib/smplkit/ws.rb', line 57

def connection_status
  @connection_status
end

Instance Method Details

#build_ws_urlObject



79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/smplkit/ws.rb', line 79

def build_ws_url
  url = @app_base_url.dup
  ws_url = if url.start_with?("https://")
             "wss://#{url[("https://".length)..]}"
           elsif url.start_with?("http://")
             "ws://#{url[("http://".length)..]}"
           else
             "wss://#{url}"
           end
  ws_url = ws_url.chomp("/")
  "#{ws_url}/api/ws/v1/events?api_key=#{@api_key}"
end

#dispatch(event_name, data) ⇒ Object



48
49
50
51
52
53
54
55
# File 'lib/smplkit/ws.rb', line 48

def dispatch(event_name, data)
  callbacks = @listeners_lock.synchronize { @listeners[event_name].dup }
  callbacks.each do |cb|
    cb.call(data)
  rescue StandardError => e
    Smplkit.debug("websocket", "listener for #{event_name} raised: #{e.class}: #{e.message}")
  end
end

#mark_connected!Object

Marked as connected for in-process testing without a real WS connection. Production wiring overrides this from the I/O thread once the gateway confirms the handshake.



62
63
64
# File 'lib/smplkit/ws.rb', line 62

def mark_connected!
  @connection_status = "connected"
end

#off(event_name, callback) ⇒ Object



44
45
46
# File 'lib/smplkit/ws.rb', line 44

def off(event_name, callback)
  @listeners_lock.synchronize { @listeners[event_name].delete(callback) }
end

#on(event_name, &callback) ⇒ Object



40
41
42
# File 'lib/smplkit/ws.rb', line 40

def on(event_name, &callback)
  @listeners_lock.synchronize { @listeners[event_name] << callback }
end

#startObject



66
67
68
69
70
71
72
# File 'lib/smplkit/ws.rb', line 66

def start
  Smplkit.debug("websocket", "starting shared WebSocket (Ruby SDK initial release: in-memory only)")
  # Live wiring is deferred. Behave as if the handshake succeeded so the
  # rest of the runtime can proceed - listeners still fire for any events
  # other code dispatches into this instance.
  mark_connected!
end

#stopObject



74
75
76
77
# File 'lib/smplkit/ws.rb', line 74

def stop
  @closed = true
  @connection_status = "disconnected"
end