forest_admin_rpc_agent 1.17.0 → 1.18.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aacf1c4f41b79f86b3b7f3a00caa96f546cc67d38031f7f550e3cb5ae4ec232
4
- data.tar.gz: bf9a970a7322632045f32487767cd0e50c8b1087c2dc702ddd01cad900f5b7bc
3
+ metadata.gz: 8ae3dd638af2e91737911aa2436d2b25c11d556863ed638b08ebfbb95ee61cdd
4
+ data.tar.gz: 1e6e61aa06ee5015b272364e9ce6f49c6e6ffb218a16ac6f981a7d1219d31767
5
5
  SHA512:
6
- metadata.gz: c8c65145002a3c4adea8332210a86e257de9a16bcc7bfecfc8ef7bab6d94cc1dafc9adddc7ea378854d1d55a5db1f2716a0110253e02e87aadfdc42b52231f3c
7
- data.tar.gz: b148bfcf994516aee7a7d72bfdaac13cdab37a222fb6181798a162589e390819563eedfd8eca4ca1e01832882c1043058d805e0478a1e0872f27fbcfbe767f97
6
+ metadata.gz: 8589283a71fb4124c12c1efa1d30bdac436cd2bb595a2c309bea55b327e639bf25cddbb3775e353d6b528f7e167e56729a295ec49d8d5aa7967da86759e1dc2e
7
+ data.tar.gz: c3419b02182fec4b8ac8078df6efc4d56abbf5dea07120fe6770e50278ad1c00f41e486464c34d7115757988773562eff0b7d19502fb89650d77d8cc59976794
@@ -1,3 +1,3 @@
1
1
  module ForestAdminRpcAgent
2
- VERSION = "1.17.0"
2
+ VERSION = "1.18.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_rpc_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.0
4
+ version: 1.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -176,10 +176,7 @@ files:
176
176
  - lib/forest_admin_rpc_agent/routes/list.rb
177
177
  - lib/forest_admin_rpc_agent/routes/native_query.rb
178
178
  - lib/forest_admin_rpc_agent/routes/schema.rb
179
- - lib/forest_admin_rpc_agent/routes/sse.rb
180
179
  - lib/forest_admin_rpc_agent/routes/update.rb
181
- - lib/forest_admin_rpc_agent/sse_connection_manager.rb
182
- - lib/forest_admin_rpc_agent/sse_streamer.rb
183
180
  - lib/forest_admin_rpc_agent/thor/install.rb
184
181
  - lib/forest_admin_rpc_agent/version.rb
185
182
  homepage: https://www.forestadmin.com
@@ -1,166 +0,0 @@
1
- require 'jsonapi-serializers'
2
-
3
- module ForestAdminRpcAgent
4
- module Routes
5
- class Sse
6
- DEFAULT_HEARTBEAT_INTERVAL = 10
7
-
8
- def initialize(url = 'sse', method = 'get', name = 'rpc_sse', heartbeat_interval: DEFAULT_HEARTBEAT_INTERVAL)
9
- @url = url
10
- @method = method
11
- @name = name
12
- @heartbeat_interval = heartbeat_interval
13
- end
14
-
15
- def registered(app)
16
- if defined?(Sinatra) && (app == Sinatra::Base || app.ancestors.include?(Sinatra::Base))
17
- register_sinatra(app)
18
- elsif defined?(Rails) && app.is_a?(ActionDispatch::Routing::Mapper)
19
- register_rails(app)
20
- else
21
- raise NotImplementedError,
22
- "Unsupported application type: #{app.class}. #{self} works with Sinatra::Base or ActionDispatch::Routing::Mapper."
23
- end
24
- end
25
-
26
- def register_sinatra(app)
27
- route_instance = self
28
- app.send(@method.to_sym, "/#{@url}") do
29
- auth_middleware = ForestAdminRpcAgent::Middleware::Authentication.new(->(_env) { [200, {}, ['OK']] })
30
- status, headers, response = auth_middleware.call(env)
31
-
32
- halt status, headers, response if status != 200
33
-
34
- content_type 'text/event-stream'
35
- headers 'Cache-Control' => 'no-cache',
36
- 'Connection' => 'keep-alive',
37
- 'X-Accel-Buffering' => 'no'
38
-
39
- stream(:keep_open) do |out|
40
- # Register this connection; any previous connection will be terminated
41
- connection = ForestAdminRpcAgent::SseConnectionManager.register_connection
42
-
43
- server_stopped = false
44
- received_signal = nil
45
- stop_proc = proc do |sig|
46
- connection.terminate
47
- server_stopped = true
48
- received_signal = sig
49
- end
50
- original_int_handler = trap('INT', stop_proc)
51
- original_term_handler = trap('TERM', stop_proc)
52
-
53
- begin
54
- streamer = SseStreamer.new(out)
55
-
56
- while connection.active?
57
- streamer.write('', event: 'heartbeat')
58
- sleep route_instance.instance_variable_get(:@heartbeat_interval)
59
- end
60
-
61
- # Send RpcServerStop only if server is stopping (not client disconnect)
62
- if server_stopped
63
- begin
64
- streamer.write({ event: 'RpcServerStop' }, event: 'RpcServerStop')
65
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] RpcServerStop event sent')
66
- rescue StandardError => e
67
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Error sending stop event: #{e.message}")
68
- end
69
- end
70
- rescue IOError, Errno::EPIPE => e
71
- # Client disconnected normally
72
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Client disconnected: #{e.message}")
73
- ensure
74
- trap('INT', original_int_handler)
75
- trap('TERM', original_term_handler)
76
- ForestAdminRpcAgent::SseConnectionManager.unregister_connection(connection)
77
- out.close if out.respond_to?(:close)
78
-
79
- # Re-send the signal to allow proper server shutdown
80
- Process.kill(received_signal, Process.pid) if received_signal
81
- end
82
- end
83
- end
84
- end
85
-
86
- def register_rails(router)
87
- route_instance = self
88
- handler = proc do |hash|
89
- request = ActionDispatch::Request.new(hash)
90
- auth_middleware = ForestAdminRpcAgent::Middleware::Authentication.new(->(_env) { [200, {}, ['OK']] })
91
- status, headers, response = auth_middleware.call(request.env)
92
-
93
- if status == 200
94
- headers = {
95
- 'Content-Type' => 'text/event-stream',
96
- 'Cache-Control' => 'no-cache',
97
- 'Connection' => 'keep-alive',
98
- 'X-Accel-Buffering' => 'no'
99
- }
100
-
101
- # Register this connection; any previous connection will be terminated
102
- connection = ForestAdminRpcAgent::SseConnectionManager.register_connection
103
-
104
- server_stopped = false
105
- received_signal = nil
106
- stop_proc = proc do |sig|
107
- connection.terminate
108
- server_stopped = true
109
- received_signal = sig
110
- end
111
- original_int_handler = trap('INT', stop_proc)
112
- original_term_handler = trap('TERM', stop_proc)
113
-
114
- body = Enumerator.new do |yielder|
115
- stream = SseStreamer.new(yielder)
116
-
117
- begin
118
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] Starting stream')
119
-
120
- while connection.active?
121
- stream.write('', event: 'heartbeat')
122
- sleep route_instance.instance_variable_get(:@heartbeat_interval)
123
- end
124
-
125
- # Send RpcServerStop only if server is stopping (not client disconnect)
126
- if server_stopped
127
- begin
128
- stream.write({ event: 'RpcServerStop' }, event: 'RpcServerStop')
129
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] RpcServerStop event sent')
130
- rescue StandardError => e
131
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Error sending stop event: #{e.message}")
132
- end
133
- end
134
- rescue IOError, Errno::EPIPE => e
135
- # Client disconnected normally
136
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Client disconnected: #{e.message}")
137
- rescue StandardError => e
138
- ForestAdminRpcAgent::Facades::Container.logger&.log('Error', "[SSE] Unexpected error: #{e.message}")
139
- ForestAdminRpcAgent::Facades::Container.logger&.log('Error', e.backtrace.join("\n"))
140
- ensure
141
- trap('INT', original_int_handler)
142
- trap('TERM', original_term_handler)
143
- ForestAdminRpcAgent::SseConnectionManager.unregister_connection(connection)
144
- ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] Stream stopped')
145
-
146
- # Re-send the signal to allow proper server shutdown
147
- Process.kill(received_signal, Process.pid) if received_signal
148
- end
149
- end
150
-
151
- [status, headers, body]
152
- else
153
- [status, headers, response]
154
- end
155
- end
156
-
157
- router.match @url,
158
- defaults: { format: 'event-stream' },
159
- to: handler,
160
- via: @method,
161
- as: @name,
162
- route_alias: @name
163
- end
164
- end
165
- end
166
- end
@@ -1,81 +0,0 @@
1
- module ForestAdminRpcAgent
2
- # Manages SSE connections to ensure only one active connection at a time.
3
- # When a new connection is established, the previous one is terminated.
4
- # This prevents zombie loops when the master restarts and reconnects.
5
- class SseConnectionManager
6
- @mutex = Mutex.new
7
- @current_connection = nil
8
-
9
- class << self
10
- # Registers a new SSE connection and terminates any existing one.
11
- # Returns a connection object that can be used to check if the connection is still active.
12
- def register_connection
13
- connection = Connection.new
14
-
15
- @mutex.synchronize do
16
- # Terminate the previous connection if it exists
17
- if @current_connection
18
- ForestAdminRpcAgent::Facades::Container.logger&.log(
19
- 'Debug',
20
- '[SSE ConnectionManager] Terminating previous connection'
21
- )
22
- @current_connection.terminate
23
- end
24
-
25
- @current_connection = connection
26
- ForestAdminRpcAgent::Facades::Container.logger&.log(
27
- 'Debug',
28
- "[SSE ConnectionManager] New connection registered (id: #{connection.id})"
29
- )
30
- end
31
-
32
- connection
33
- end
34
-
35
- # Unregisters a connection when it's closed normally.
36
- def unregister_connection(connection)
37
- @mutex.synchronize do
38
- if @current_connection&.id == connection.id
39
- @current_connection = nil
40
- ForestAdminRpcAgent::Facades::Container.logger&.log(
41
- 'Debug',
42
- "[SSE ConnectionManager] Connection unregistered (id: #{connection.id})"
43
- )
44
- end
45
- end
46
- end
47
-
48
- # Returns the current active connection (for testing purposes)
49
- def current_connection
50
- @mutex.synchronize { @current_connection }
51
- end
52
-
53
- # Resets the manager state (for testing purposes)
54
- def reset!
55
- @mutex.synchronize do
56
- @current_connection&.terminate
57
- @current_connection = nil
58
- end
59
- end
60
- end
61
-
62
- # Represents an individual SSE connection
63
- class Connection
64
- attr_reader :id
65
-
66
- def initialize
67
- @id = SecureRandom.uuid
68
- @active = true
69
- @mutex = Mutex.new
70
- end
71
-
72
- def active?
73
- @mutex.synchronize { @active }
74
- end
75
-
76
- def terminate
77
- @mutex.synchronize { @active = false }
78
- end
79
- end
80
- end
81
- end
@@ -1,14 +0,0 @@
1
- require 'json'
2
-
3
- module ForestAdminRpcAgent
4
- class SseStreamer
5
- def initialize(yielder)
6
- @yielder = yielder
7
- end
8
-
9
- def write(object, event: nil)
10
- @yielder << "event: #{event}\n" if event
11
- @yielder << "data: #{JSON.dump(object)}\n\n"
12
- end
13
- end
14
- end