bolt_rb 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/bolt_rb/socket_mode/client.rb +67 -6
- data/lib/bolt_rb/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e218118e1e764bfe65750f32f82c4671e3c1f777dfe50420889ba51758c179f
|
|
4
|
+
data.tar.gz: a39610870555d4bade286a77431583c5944bfbad836854368776e6f236b9ffc7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cdbcce81133b4e5fb4720ce93e82ac041f1ae84db8b2e17f3bf4bab3a98dafad0a00e26c6def32f51d1709b0641c46de9acc51982acd77a0f6ddd21f123fcb8
|
|
7
|
+
data.tar.gz: 35b7cc610cd8ade5ce7900f6ad2d55d548a68c318487388e24f65245619e1f1dc4340b87405de6f2576bd8a9b3b51dbd3606153b873f9b8973813999566f3d1a
|
|
@@ -25,6 +25,8 @@ module BoltRb
|
|
|
25
25
|
class Client
|
|
26
26
|
SLACK_API_URL = 'https://slack.com/api/apps.connections.open'
|
|
27
27
|
RECONNECT_DELAY = 5
|
|
28
|
+
# If no messages received in this many seconds, assume zombie socket
|
|
29
|
+
CONNECTION_STALE_THRESHOLD = 45
|
|
28
30
|
|
|
29
31
|
# @return [String] The Slack app-level token
|
|
30
32
|
attr_reader :app_token
|
|
@@ -42,6 +44,7 @@ module BoltRb
|
|
|
42
44
|
@running = false
|
|
43
45
|
@websocket = nil
|
|
44
46
|
@message_handlers = []
|
|
47
|
+
@last_message_at = nil
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
# Registers a handler for incoming messages
|
|
@@ -104,11 +107,18 @@ module BoltRb
|
|
|
104
107
|
|
|
105
108
|
while @running
|
|
106
109
|
sleep 0.1
|
|
110
|
+
|
|
111
|
+
# Check for zombie socket - library reports open but no messages received
|
|
112
|
+
if connection_stale?
|
|
113
|
+
logger.warn "[SocketMode] Connection stale (no messages in #{CONNECTION_STALE_THRESHOLD}s), forcing reconnect"
|
|
114
|
+
force_reconnect
|
|
115
|
+
end
|
|
116
|
+
|
|
107
117
|
reconnect_if_needed
|
|
108
118
|
|
|
109
119
|
# Periodic heartbeat to confirm the loop is alive
|
|
110
120
|
if Time.now - last_heartbeat >= heartbeat_interval
|
|
111
|
-
logger.debug "[SocketMode] Heartbeat: connected=#{connected?}, websocket_open=#{@websocket&.open?}"
|
|
121
|
+
logger.debug "[SocketMode] Heartbeat: connected=#{connected?}, websocket_open=#{@websocket&.open?}, last_msg=#{@last_message_at&.strftime('%H:%M:%S') || 'never'}"
|
|
112
122
|
last_heartbeat = Time.now
|
|
113
123
|
end
|
|
114
124
|
end
|
|
@@ -128,6 +138,31 @@ module BoltRb
|
|
|
128
138
|
connect_with_retry
|
|
129
139
|
end
|
|
130
140
|
|
|
141
|
+
# Checks if the connection appears stale (zombie socket)
|
|
142
|
+
#
|
|
143
|
+
# Returns true if we have an apparently open connection but haven't
|
|
144
|
+
# received any messages in CONNECTION_STALE_THRESHOLD seconds
|
|
145
|
+
#
|
|
146
|
+
# @return [Boolean]
|
|
147
|
+
def connection_stale?
|
|
148
|
+
return false unless @websocket&.open?
|
|
149
|
+
return false if @last_message_at.nil?
|
|
150
|
+
|
|
151
|
+
Time.now - @last_message_at > CONNECTION_STALE_THRESHOLD
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Forces a reconnection by closing the current socket
|
|
155
|
+
#
|
|
156
|
+
# Used when we detect a zombie socket that reports open but isn't
|
|
157
|
+
# actually receiving messages
|
|
158
|
+
#
|
|
159
|
+
# @return [void]
|
|
160
|
+
def force_reconnect
|
|
161
|
+
@websocket&.close
|
|
162
|
+
@last_message_at = nil
|
|
163
|
+
# reconnect_if_needed will pick this up on the next loop iteration
|
|
164
|
+
end
|
|
165
|
+
|
|
131
166
|
# Attempts to connect with retry logic
|
|
132
167
|
#
|
|
133
168
|
# @return [void]
|
|
@@ -207,6 +242,7 @@ module BoltRb
|
|
|
207
242
|
#
|
|
208
243
|
# @return [void]
|
|
209
244
|
def handle_open
|
|
245
|
+
@last_message_at = Time.now
|
|
210
246
|
logger.info '[SocketMode] Connected to Slack'
|
|
211
247
|
end
|
|
212
248
|
|
|
@@ -215,10 +251,21 @@ module BoltRb
|
|
|
215
251
|
# @param msg [WebSocket::Client::Simple::Message] The message
|
|
216
252
|
# @return [void]
|
|
217
253
|
def handle_message(msg)
|
|
218
|
-
|
|
254
|
+
# Track message receipt for connection health monitoring
|
|
255
|
+
@last_message_at = Time.now
|
|
256
|
+
|
|
257
|
+
# Handle WebSocket protocol-level ping frames (Opcode 0x9)
|
|
258
|
+
# Must respond with pong frame echoing the same payload
|
|
259
|
+
if msg.type == :ping
|
|
260
|
+
handle_websocket_ping(msg.data)
|
|
261
|
+
return
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
raw_data = msg.data
|
|
265
|
+
logger.debug "[SocketMode] Raw message received (type=#{msg.type}): #{raw_data.nil? ? '(nil)' : raw_data[0, 200]}"
|
|
219
266
|
|
|
220
|
-
# Skip nil, empty, or non-JSON data
|
|
221
|
-
return if
|
|
267
|
+
# Skip nil, empty, or non-JSON data
|
|
268
|
+
return if raw_data.nil? || raw_data.empty? || !raw_data.start_with?('{')
|
|
222
269
|
|
|
223
270
|
data = JSON.parse(msg.data)
|
|
224
271
|
|
|
@@ -264,7 +311,7 @@ module BoltRb
|
|
|
264
311
|
logger.info "[SocketMode] WebSocket closed: #{event}"
|
|
265
312
|
end
|
|
266
313
|
|
|
267
|
-
# Handles Slack Socket Mode ping message
|
|
314
|
+
# Handles Slack Socket Mode JSON ping message
|
|
268
315
|
#
|
|
269
316
|
# Responds with a pong message echoing back the num field
|
|
270
317
|
# @param data [Hash] The ping message data
|
|
@@ -275,7 +322,21 @@ module BoltRb
|
|
|
275
322
|
pong = { 'type' => 'pong' }
|
|
276
323
|
pong['num'] = data['num'] if data['num']
|
|
277
324
|
@websocket.send(pong.to_json)
|
|
278
|
-
logger.debug "[SocketMode]
|
|
325
|
+
logger.debug "[SocketMode] Sent pong response#{data['num'] ? " (num: #{data['num']})" : ''}"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# Handles WebSocket protocol-level ping frames (Opcode 0x9)
|
|
329
|
+
#
|
|
330
|
+
# Per WebSocket RFC 6455, we must respond with a pong frame (Opcode 0xA)
|
|
331
|
+
# that echoes back the exact payload from the ping frame.
|
|
332
|
+
# @param payload [String] The ping frame payload to echo back
|
|
333
|
+
# @return [void]
|
|
334
|
+
def handle_websocket_ping(payload)
|
|
335
|
+
return unless @websocket&.open?
|
|
336
|
+
|
|
337
|
+
logger.debug "[SocketMode] WebSocket ping received: '#{payload}'"
|
|
338
|
+
@websocket.send(payload, type: :pong)
|
|
339
|
+
logger.debug "[SocketMode] Sent WebSocket pong frame: '#{payload}'"
|
|
279
340
|
end
|
|
280
341
|
|
|
281
342
|
# Sends an acknowledgement for an event
|
data/lib/bolt_rb/version.rb
CHANGED