flow_chat 0.6.1 → 0.7.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +44 -0
  3. data/.gitignore +2 -1
  4. data/README.md +84 -1229
  5. data/docs/configuration.md +337 -0
  6. data/docs/flows.md +320 -0
  7. data/docs/images/simulator.png +0 -0
  8. data/docs/instrumentation.md +216 -0
  9. data/docs/media.md +153 -0
  10. data/docs/testing.md +475 -0
  11. data/docs/ussd-setup.md +306 -0
  12. data/docs/whatsapp-setup.md +162 -0
  13. data/examples/multi_tenant_whatsapp_controller.rb +9 -37
  14. data/examples/simulator_controller.rb +9 -18
  15. data/examples/ussd_controller.rb +32 -38
  16. data/examples/whatsapp_controller.rb +32 -125
  17. data/examples/whatsapp_media_examples.rb +68 -336
  18. data/examples/whatsapp_message_job.rb +5 -3
  19. data/flow_chat.gemspec +6 -2
  20. data/lib/flow_chat/base_processor.rb +48 -2
  21. data/lib/flow_chat/config.rb +5 -0
  22. data/lib/flow_chat/context.rb +13 -1
  23. data/lib/flow_chat/instrumentation/log_subscriber.rb +176 -0
  24. data/lib/flow_chat/instrumentation/metrics_collector.rb +197 -0
  25. data/lib/flow_chat/instrumentation/setup.rb +155 -0
  26. data/lib/flow_chat/instrumentation.rb +70 -0
  27. data/lib/flow_chat/prompt.rb +20 -20
  28. data/lib/flow_chat/session/cache_session_store.rb +73 -7
  29. data/lib/flow_chat/session/middleware.rb +37 -4
  30. data/lib/flow_chat/session/rails_session_store.rb +36 -1
  31. data/lib/flow_chat/simulator/controller.rb +6 -6
  32. data/lib/flow_chat/ussd/gateway/nalo.rb +30 -0
  33. data/lib/flow_chat/ussd/gateway/nsano.rb +33 -0
  34. data/lib/flow_chat/ussd/middleware/choice_mapper.rb +109 -0
  35. data/lib/flow_chat/ussd/middleware/executor.rb +24 -2
  36. data/lib/flow_chat/ussd/middleware/pagination.rb +87 -7
  37. data/lib/flow_chat/ussd/processor.rb +14 -0
  38. data/lib/flow_chat/ussd/renderer.rb +1 -1
  39. data/lib/flow_chat/version.rb +1 -1
  40. data/lib/flow_chat/whatsapp/client.rb +99 -12
  41. data/lib/flow_chat/whatsapp/configuration.rb +35 -4
  42. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +120 -34
  43. data/lib/flow_chat/whatsapp/middleware/executor.rb +24 -2
  44. data/lib/flow_chat/whatsapp/processor.rb +8 -0
  45. data/lib/flow_chat/whatsapp/renderer.rb +4 -9
  46. data/lib/flow_chat.rb +23 -0
  47. metadata +22 -11
  48. data/.travis.yml +0 -6
  49. data/app/controllers/demo_controller.rb +0 -101
  50. data/app/flow_chat/demo_restaurant_flow.rb +0 -889
  51. data/config/routes_demo.rb +0 -59
  52. data/examples/initializer.rb +0 -86
  53. data/examples/media_prompts_examples.rb +0 -27
  54. data/images/ussd_simulator.png +0 -0
@@ -10,26 +10,39 @@ module FlowChat
10
10
 
11
11
  module Gateway
12
12
  class CloudApi
13
+ include FlowChat::Instrumentation
14
+
15
+ attr_reader :context
16
+
13
17
  def initialize(app, config = nil)
14
18
  @app = app
15
19
  @config = config || FlowChat::Whatsapp::Configuration.from_credentials
16
20
  @client = FlowChat::Whatsapp::Client.new(@config)
21
+
22
+ FlowChat.logger.info { "CloudApi: Initialized WhatsApp Cloud API gateway with phone_number_id: #{@config.phone_number_id}" }
23
+ FlowChat.logger.debug { "CloudApi: Gateway configuration - API base URL: #{FlowChat::Config.whatsapp.api_base_url}" }
17
24
  end
18
25
 
19
26
  def call(context)
27
+ @context = context
20
28
  controller = context.controller
21
29
  request = controller.request
22
30
 
31
+ FlowChat.logger.debug { "CloudApi: Processing #{request.request_method} request to #{request.path}" }
32
+
23
33
  # Handle webhook verification
24
34
  if request.get? && request.params["hub.mode"] == "subscribe"
35
+ FlowChat.logger.info { "CloudApi: Handling webhook verification request" }
25
36
  return handle_verification(context)
26
37
  end
27
38
 
28
39
  # Handle webhook messages
29
40
  if request.post?
41
+ FlowChat.logger.info { "CloudApi: Handling webhook message" }
30
42
  return handle_webhook(context)
31
43
  end
32
44
 
45
+ FlowChat.logger.warn { "CloudApi: Invalid request method or parameters - returning bad request" }
33
46
  controller.head :bad_request
34
47
  end
35
48
 
@@ -41,11 +54,14 @@ module FlowChat
41
54
  def determine_message_handler(context)
42
55
  # Check if simulator mode was already detected and set in context
43
56
  if context["simulator_mode"]
57
+ FlowChat.logger.debug { "CloudApi: Using simulator message handler" }
44
58
  return :simulator
45
59
  end
46
60
 
47
61
  # Use global WhatsApp configuration
48
- FlowChat::Config.whatsapp.message_handling_mode
62
+ mode = FlowChat::Config.whatsapp.message_handling_mode
63
+ FlowChat.logger.debug { "CloudApi: Using #{mode} message handling mode" }
64
+ mode
49
65
  end
50
66
 
51
67
  def handle_verification(context)
@@ -53,63 +69,107 @@ module FlowChat
53
69
  params = controller.request.params
54
70
 
55
71
  verify_token = @config.verify_token
72
+ provided_token = params["hub.verify_token"]
73
+ challenge = params["hub.challenge"]
56
74
 
57
- if params["hub.verify_token"] == verify_token
58
- controller.render plain: params["hub.challenge"]
75
+ FlowChat.logger.debug { "CloudApi: Webhook verification - provided token matches: #{provided_token == verify_token}" }
76
+
77
+ if provided_token == verify_token
78
+ # Use instrumentation for webhook verification success
79
+ instrument(Events::WEBHOOK_VERIFIED, {
80
+ challenge: challenge,
81
+ platform: :whatsapp
82
+ })
83
+
84
+ controller.render plain: challenge
59
85
  else
86
+ # Use instrumentation for webhook verification failure
87
+ instrument(Events::WEBHOOK_FAILED, {
88
+ reason: "Invalid verify token",
89
+ platform: :whatsapp
90
+ })
91
+
60
92
  controller.head :forbidden
61
93
  end
62
94
  end
63
95
 
64
96
  def handle_webhook(context)
65
97
  controller = context.controller
66
-
98
+
67
99
  # Parse body
68
100
  begin
69
101
  parse_request_body(controller.request)
102
+ FlowChat.logger.debug { "CloudApi: Successfully parsed webhook request body" }
70
103
  rescue JSON::ParserError => e
71
- Rails.logger.warn "Failed to parse webhook body: #{e.message}"
104
+ FlowChat.logger.error { "CloudApi: Failed to parse webhook body: #{e.message}" }
72
105
  return controller.head :bad_request
73
106
  end
74
-
107
+
75
108
  # Check for simulator mode parameter in request (before validation)
76
109
  # But only enable if valid simulator token is provided
77
110
  is_simulator_mode = simulate?(context)
78
111
  if is_simulator_mode
112
+ FlowChat.logger.info { "CloudApi: Simulator mode enabled for this request" }
79
113
  context["simulator_mode"] = true
80
114
  end
81
115
 
82
116
  # Validate webhook signature for security (skip for simulator mode)
83
117
  unless is_simulator_mode || valid_webhook_signature?(controller.request)
84
- Rails.logger.warn "Invalid webhook signature received"
118
+ FlowChat.logger.warn { "CloudApi: Invalid webhook signature received - rejecting request" }
85
119
  return controller.head :unauthorized
86
120
  end
87
121
 
122
+ FlowChat.logger.debug { "CloudApi: Webhook signature validation passed" }
123
+
88
124
  # Extract message data from WhatsApp webhook
89
125
  entry = @body.dig("entry", 0)
90
- return controller.head :ok unless entry
126
+ unless entry
127
+ FlowChat.logger.debug { "CloudApi: No entry found in webhook body - returning OK" }
128
+ return controller.head :ok
129
+ end
91
130
 
92
131
  changes = entry.dig("changes", 0)
93
- return controller.head :ok unless changes
132
+ unless changes
133
+ FlowChat.logger.debug { "CloudApi: No changes found in webhook entry - returning OK" }
134
+ return controller.head :ok
135
+ end
94
136
 
95
137
  value = changes["value"]
96
- return controller.head :ok unless value
138
+ unless value
139
+ FlowChat.logger.debug { "CloudApi: No value found in webhook changes - returning OK" }
140
+ return controller.head :ok
141
+ end
97
142
 
98
143
  # Handle incoming messages
99
144
  if value["messages"]&.any?
100
145
  message = value["messages"].first
101
146
  contact = value["contacts"]&.first
102
147
 
103
- context["request.id"] = message["from"]
148
+ phone_number = message["from"]
149
+ message_id = message["id"]
150
+ contact_name = contact&.dig("profile", "name")
151
+
152
+ # Use instrumentation for message received
153
+ instrument(Events::MESSAGE_RECEIVED, {
154
+ from: phone_number,
155
+ message: context.input,
156
+ message_type: message["type"],
157
+ message_id: message_id,
158
+ platform: :whatsapp
159
+ })
160
+
161
+ context["request.id"] = phone_number
104
162
  context["request.gateway"] = :whatsapp_cloud_api
105
- context["request.message_id"] = message["id"]
106
- context["request.msisdn"] = Phonelib.parse(message["from"]).e164
107
- context["request.contact_name"] = contact&.dig("profile", "name")
163
+ context["request.message_id"] = message_id
164
+ context["request.msisdn"] = Phonelib.parse(phone_number).e164
165
+ context["request.contact_name"] = contact_name
108
166
  context["request.timestamp"] = message["timestamp"]
109
167
 
110
168
  # Extract message content based on type
111
169
  extract_message_content(message, context)
112
170
 
171
+ FlowChat.logger.debug { "CloudApi: Message content extracted - Type: #{message["type"]}, Input: '#{context.input}'" }
172
+
113
173
  # Determine message handling mode
114
174
  handler_mode = determine_message_handler(context)
115
175
 
@@ -127,7 +187,9 @@ module FlowChat
127
187
 
128
188
  # Handle message status updates
129
189
  if value["statuses"]&.any?
130
- Rails.logger.info "WhatsApp status update: #{value["statuses"]}"
190
+ statuses = value["statuses"]
191
+ FlowChat.logger.info { "CloudApi: Received #{statuses.size} status update(s)" }
192
+ FlowChat.logger.debug { "CloudApi: Status updates: #{statuses.inspect}" }
131
193
  end
132
194
 
133
195
  controller.head :ok
@@ -137,18 +199,23 @@ module FlowChat
137
199
  def valid_webhook_signature?(request)
138
200
  # Check if signature validation is explicitly disabled
139
201
  if @config.skip_signature_validation
202
+ FlowChat.logger.debug { "CloudApi: Webhook signature validation is disabled" }
140
203
  return true
141
204
  end
142
205
 
143
206
  # Require app_secret for signature validation
144
207
  unless @config.app_secret && !@config.app_secret.empty?
145
- raise FlowChat::Whatsapp::ConfigurationError,
146
- "WhatsApp app_secret is required for webhook signature validation. " \
147
- "Either configure app_secret or set skip_signature_validation=true to explicitly disable validation."
208
+ error_msg = "WhatsApp app_secret is required for webhook signature validation. " \
209
+ "Either configure app_secret or set skip_signature_validation=true to explicitly disable validation."
210
+ FlowChat.logger.error { "CloudApi: #{error_msg}" }
211
+ raise FlowChat::Whatsapp::ConfigurationError, error_msg
148
212
  end
149
213
 
150
214
  signature_header = request.headers["X-Hub-Signature-256"]
151
- return false unless signature_header
215
+ unless signature_header
216
+ FlowChat.logger.warn { "CloudApi: No X-Hub-Signature-256 header found in request" }
217
+ return false
218
+ end
152
219
 
153
220
  # Extract signature from header (format: "sha256=<signature>")
154
221
  expected_signature = signature_header.sub("sha256=", "")
@@ -166,11 +233,19 @@ module FlowChat
166
233
  )
167
234
 
168
235
  # Compare signatures using secure comparison to prevent timing attacks
169
- secure_compare(expected_signature, calculated_signature)
236
+ signature_valid = secure_compare(expected_signature, calculated_signature)
237
+
238
+ if signature_valid
239
+ FlowChat.logger.debug { "CloudApi: Webhook signature validation successful" }
240
+ else
241
+ FlowChat.logger.warn { "CloudApi: Webhook signature validation failed - signatures do not match" }
242
+ end
243
+
244
+ signature_valid
170
245
  rescue FlowChat::Whatsapp::ConfigurationError
171
246
  raise
172
247
  rescue => e
173
- Rails.logger.error "Error validating webhook signature: #{e.message}"
248
+ FlowChat.logger.error { "CloudApi: Error validating webhook signature: #{e.class.name}: #{e.message}" }
174
249
  false
175
250
  end
176
251
 
@@ -185,24 +260,35 @@ module FlowChat
185
260
  end
186
261
 
187
262
  def extract_message_content(message, context)
188
- case message["type"]
263
+ message_type = message["type"]
264
+ FlowChat.logger.debug { "CloudApi: Extracting content from #{message_type} message" }
265
+
266
+ case message_type
189
267
  when "text"
190
- context.input = message.dig("text", "body")
268
+ content = message.dig("text", "body")
269
+ context.input = content
270
+ FlowChat.logger.debug { "CloudApi: Text message content: '#{content}'" }
191
271
  when "interactive"
192
272
  # Handle button/list replies
193
273
  if message.dig("interactive", "type") == "button_reply"
194
- context.input = message.dig("interactive", "button_reply", "id")
274
+ content = message.dig("interactive", "button_reply", "id")
275
+ context.input = content
276
+ FlowChat.logger.debug { "CloudApi: Button reply ID: '#{content}'" }
195
277
  elsif message.dig("interactive", "type") == "list_reply"
196
- context.input = message.dig("interactive", "list_reply", "id")
278
+ content = message.dig("interactive", "list_reply", "id")
279
+ context.input = content
280
+ FlowChat.logger.debug { "CloudApi: List reply ID: '#{content}'" }
197
281
  end
198
282
  when "location"
199
- context["request.location"] = {
283
+ location = {
200
284
  latitude: message.dig("location", "latitude"),
201
285
  longitude: message.dig("location", "longitude"),
202
286
  name: message.dig("location", "name"),
203
287
  address: message.dig("location", "address")
204
288
  }
289
+ context["request.location"] = location
205
290
  context.input = "$location$"
291
+ FlowChat.logger.debug { "CloudApi: Location received - Lat: #{location[:latitude]}, Lng: #{location[:longitude]}" }
206
292
  when "image", "document", "audio", "video"
207
293
  context["request.media"] = {
208
294
  type: message["type"],
@@ -231,7 +317,7 @@ module FlowChat
231
317
  if response
232
318
  _type, prompt, choices, media = response
233
319
  rendered_message = render_response(prompt, choices, media)
234
-
320
+
235
321
  # Queue only the response delivery asynchronously
236
322
  send_data = {
237
323
  msisdn: context["request.msisdn"],
@@ -261,7 +347,7 @@ module FlowChat
261
347
  if response
262
348
  _type, prompt, choices, media = response
263
349
  rendered_message = render_response(prompt, choices, media)
264
-
350
+
265
351
  # For simulator mode, return the response data in the HTTP response
266
352
  # instead of actually sending via WhatsApp API
267
353
  message_payload = @client.build_message_payload(rendered_message, context["request.msisdn"])
@@ -285,7 +371,7 @@ module FlowChat
285
371
  def simulate?(context)
286
372
  # Check if simulator mode is enabled for this processor
287
373
  return false unless context["enable_simulator"]
288
-
374
+
289
375
  # Then check if simulator mode is requested and valid
290
376
  @body.dig("simulator_mode") && valid_simulator_cookie?(context)
291
377
  end
@@ -293,27 +379,27 @@ module FlowChat
293
379
  def valid_simulator_cookie?(context)
294
380
  simulator_secret = FlowChat::Config.simulator_secret
295
381
  return false unless simulator_secret && !simulator_secret.empty?
296
-
382
+
297
383
  # Check for simulator cookie
298
384
  request = context.controller.request
299
385
  simulator_cookie = request.cookies["flowchat_simulator"]
300
386
  return false unless simulator_cookie
301
-
387
+
302
388
  # Verify the cookie is a valid HMAC signature
303
389
  # Cookie format: "timestamp:signature" where signature = HMAC(simulator_secret, "simulator:timestamp")
304
390
  begin
305
391
  timestamp_str, signature = simulator_cookie.split(":", 2)
306
392
  return false unless timestamp_str && signature
307
-
393
+
308
394
  # Check timestamp is recent (within 24 hours for reasonable session duration)
309
395
  timestamp = timestamp_str.to_i
310
396
  return false if timestamp <= 0
311
397
  return false if (Time.now.to_i - timestamp).abs > 86400 # 24 hours
312
-
398
+
313
399
  # Calculate expected signature
314
400
  message = "simulator:#{timestamp_str}"
315
401
  expected_signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), simulator_secret, message)
316
-
402
+
317
403
  # Use secure comparison
318
404
  secure_compare(signature, expected_signature)
319
405
  rescue => e
@@ -4,24 +4,46 @@ module FlowChat
4
4
  class Executor
5
5
  def initialize(app)
6
6
  @app = app
7
+ FlowChat.logger.debug { "Whatsapp::Executor: Initialized WhatsApp executor middleware" }
7
8
  end
8
9
 
9
10
  def call(context)
11
+ flow_class = context.flow
12
+ action = context["flow.action"]
13
+ session_id = context["session.id"]
14
+
15
+ FlowChat.logger.info { "Whatsapp::Executor: Executing flow #{flow_class.name}##{action} for session #{session_id}" }
16
+
10
17
  whatsapp_app = build_whatsapp_app context
11
- flow = context.flow.new whatsapp_app
12
- flow.send context["flow.action"]
18
+ FlowChat.logger.debug { "Whatsapp::Executor: WhatsApp app built for flow execution" }
19
+
20
+ flow = flow_class.new whatsapp_app
21
+ FlowChat.logger.debug { "Whatsapp::Executor: Flow instance created, invoking #{action} method" }
22
+
23
+ flow.send action
24
+ FlowChat.logger.warn { "Whatsapp::Executor: Flow execution failed to interact with user for #{flow_class.name}##{action}" }
25
+ raise FlowChat::Interrupt::Terminate, "Unexpected end of flow."
13
26
  rescue FlowChat::Interrupt::Prompt => e
27
+ FlowChat.logger.info { "Whatsapp::Executor: Flow prompted user - Session: #{session_id}, Prompt: '#{e.prompt.truncate(100)}'" }
28
+ FlowChat.logger.debug { "Whatsapp::Executor: Prompt details - Choices: #{e.choices&.size || 0}, Has media: #{!e.media.nil?}" }
14
29
  # Return the same triplet format as USSD for consistency
15
30
  [:prompt, e.prompt, e.choices, e.media]
16
31
  rescue FlowChat::Interrupt::Terminate => e
32
+ FlowChat.logger.info { "Whatsapp::Executor: Flow terminated - Session: #{session_id}, Message: '#{e.prompt.truncate(100)}'" }
33
+ FlowChat.logger.debug { "Whatsapp::Executor: Destroying session #{session_id}" }
17
34
  # Clean up session and return terminal message
18
35
  context.session.destroy
19
36
  [:terminate, e.prompt, nil, e.media]
37
+ rescue => error
38
+ FlowChat.logger.error { "Whatsapp::Executor: Flow execution failed - #{flow_class.name}##{action}, Session: #{session_id}, Error: #{error.class.name}: #{error.message}" }
39
+ FlowChat.logger.debug { "Whatsapp::Executor: Stack trace: #{error.backtrace.join("\n")}" }
40
+ raise
20
41
  end
21
42
 
22
43
  private
23
44
 
24
45
  def build_whatsapp_app(context)
46
+ FlowChat.logger.debug { "Whatsapp::Executor: Building WhatsApp app instance" }
25
47
  FlowChat::Whatsapp::App.new(context)
26
48
  end
27
49
  end
@@ -2,6 +2,7 @@ module FlowChat
2
2
  module Whatsapp
3
3
  class Processor < FlowChat::BaseProcessor
4
4
  def use_whatsapp_config(config)
5
+ FlowChat.logger.debug { "Whatsapp::Processor: Configuring WhatsApp config: #{config.class.name}" }
5
6
  @whatsapp_config = config
6
7
  self
7
8
  end
@@ -13,13 +14,20 @@ module FlowChat
13
14
  end
14
15
 
15
16
  def build_middleware_stack
17
+ FlowChat.logger.debug { "Whatsapp::Processor: Building WhatsApp middleware stack" }
16
18
  create_middleware_stack("whatsapp")
17
19
  end
18
20
 
19
21
  def configure_middleware_stack(builder)
22
+ FlowChat.logger.debug { "Whatsapp::Processor: Configuring WhatsApp middleware stack" }
20
23
  builder.use FlowChat::Session::Middleware
24
+ FlowChat.logger.debug { "Whatsapp::Processor: Added Session::Middleware" }
25
+
21
26
  builder.use middleware
27
+ FlowChat.logger.debug { "Whatsapp::Processor: Added custom middleware" }
28
+
22
29
  builder.use FlowChat::Whatsapp::Middleware::Executor
30
+ FlowChat.logger.debug { "Whatsapp::Processor: Added Whatsapp::Middleware::Executor" }
23
31
  end
24
32
  end
25
33
  end
@@ -49,15 +49,10 @@ module FlowChat
49
49
  end
50
50
 
51
51
  def build_selection_message
52
- # Determine the best way to present choices
53
- if choices.is_a?(Array)
54
- # Convert array to hash with index-based keys
55
- choice_hash = choices.each_with_index.to_h { |choice, index| [index.to_s, choice] }
56
- build_interactive_message(choice_hash)
57
- elsif choices.is_a?(Hash)
52
+ if choices.is_a?(Hash)
58
53
  build_interactive_message(choices)
59
54
  else
60
- raise ArgumentError, "choices must be an Array or Hash"
55
+ raise ArgumentError, "choices must be a Hash"
61
56
  end
62
57
  end
63
58
 
@@ -121,7 +116,7 @@ module FlowChat
121
116
  }
122
117
  when :video
123
118
  {
124
- type: "video",
119
+ type: "video",
125
120
  video: {link: url}
126
121
  }
127
122
  when :document
@@ -188,4 +183,4 @@ module FlowChat
188
183
  end
189
184
  end
190
185
  end
191
- end
186
+ end
data/lib/flow_chat.rb CHANGED
@@ -2,6 +2,8 @@ require "zeitwerk"
2
2
  require "active_support"
3
3
  require "active_support/core_ext/time"
4
4
  require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/string/filters"
6
+ require "active_support/core_ext/enumerable"
5
7
 
6
8
  loader = Zeitwerk::Loader.for_gem
7
9
  loader.enable_reloading if defined?(Rails.env) && Rails.env.development?
@@ -11,6 +13,27 @@ module FlowChat
11
13
  def self.root
12
14
  Pathname.new __dir__
13
15
  end
16
+
17
+ def self.setup_instrumentation!
18
+ require_relative "flow_chat/instrumentation/setup"
19
+ FlowChat::Instrumentation::Setup.setup_instrumentation!
20
+ end
21
+
22
+ # Access to instrumentation
23
+ def self.instrument(event_name, payload = {}, &block)
24
+ FlowChat::Instrumentation.instrument(event_name, payload, &block)
25
+ end
26
+
27
+ def self.metrics
28
+ FlowChat::Instrumentation::Setup.metrics_collector
29
+ end
14
30
  end
15
31
 
16
32
  loader.eager_load
33
+
34
+ # Auto-setup instrumentation in Rails environments
35
+ if defined?(Rails)
36
+ Rails.application.config.after_initialize do
37
+ FlowChat.setup_instrumentation!
38
+ end
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flow_chat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-06-05 00:00:00.000000000 Z
11
+ date: 2025-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -80,7 +80,11 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 0.4.2
83
- description: Framework for building Menu based conversations (e.g. USSD) in Rails.
83
+ description: "FlowChat is a Rails framework for building sophisticated conversational
84
+ interfaces across USSD and WhatsApp platforms. \nCreate interactive flows with menus,
85
+ prompts, validation, media support, and session management. Features include \nmulti-tenancy,
86
+ background job processing, built-in simulator for testing, and comprehensive middleware
87
+ support.\n"
84
88
  email:
85
89
  - sfroelich01@gmail.com
86
90
  executables: []
@@ -88,21 +92,24 @@ extensions: []
88
92
  extra_rdoc_files: []
89
93
  files:
90
94
  - ".DS_Store"
95
+ - ".github/workflows/ci.yml"
91
96
  - ".gitignore"
92
97
  - ".ruby-version"
93
- - ".travis.yml"
94
98
  - Gemfile
95
99
  - LICENSE.txt
96
100
  - README.md
97
101
  - Rakefile
98
102
  - SECURITY.md
99
- - app/controllers/demo_controller.rb
100
- - app/flow_chat/demo_restaurant_flow.rb
101
103
  - bin/console
102
104
  - bin/setup
103
- - config/routes_demo.rb
104
- - examples/initializer.rb
105
- - examples/media_prompts_examples.rb
105
+ - docs/configuration.md
106
+ - docs/flows.md
107
+ - docs/images/simulator.png
108
+ - docs/instrumentation.md
109
+ - docs/media.md
110
+ - docs/testing.md
111
+ - docs/ussd-setup.md
112
+ - docs/whatsapp-setup.md
106
113
  - examples/multi_tenant_whatsapp_controller.rb
107
114
  - examples/simulator_controller.rb
108
115
  - examples/ussd_controller.rb
@@ -110,12 +117,15 @@ files:
110
117
  - examples/whatsapp_media_examples.rb
111
118
  - examples/whatsapp_message_job.rb
112
119
  - flow_chat.gemspec
113
- - images/ussd_simulator.png
114
120
  - lib/flow_chat.rb
115
121
  - lib/flow_chat/base_processor.rb
116
122
  - lib/flow_chat/config.rb
117
123
  - lib/flow_chat/context.rb
118
124
  - lib/flow_chat/flow.rb
125
+ - lib/flow_chat/instrumentation.rb
126
+ - lib/flow_chat/instrumentation/log_subscriber.rb
127
+ - lib/flow_chat/instrumentation/metrics_collector.rb
128
+ - lib/flow_chat/instrumentation/setup.rb
119
129
  - lib/flow_chat/interrupt.rb
120
130
  - lib/flow_chat/prompt.rb
121
131
  - lib/flow_chat/session/cache_session_store.rb
@@ -126,6 +136,7 @@ files:
126
136
  - lib/flow_chat/ussd/app.rb
127
137
  - lib/flow_chat/ussd/gateway/nalo.rb
128
138
  - lib/flow_chat/ussd/gateway/nsano.rb
139
+ - lib/flow_chat/ussd/middleware/choice_mapper.rb
129
140
  - lib/flow_chat/ussd/middleware/executor.rb
130
141
  - lib/flow_chat/ussd/middleware/pagination.rb
131
142
  - lib/flow_chat/ussd/middleware/resumable_session.rb
@@ -167,5 +178,5 @@ requirements: []
167
178
  rubygems_version: 3.4.10
168
179
  signing_key:
169
180
  specification_version: 4
170
- summary: Framework for building Menu based conversations (e.g. USSD) in Rails.
181
+ summary: Build conversational interfaces for USSD and WhatsApp with Rails
171
182
  test_files: []
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.7.1
6
- before_install: gem install bundler -v 2.1.4