flow_chat 0.6.1 โ†’ 0.8.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +44 -0
  3. data/.gitignore +2 -1
  4. data/README.md +85 -1229
  5. data/docs/configuration.md +360 -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/sessions.md +433 -0
  11. data/docs/testing.md +475 -0
  12. data/docs/ussd-setup.md +322 -0
  13. data/docs/whatsapp-setup.md +162 -0
  14. data/examples/multi_tenant_whatsapp_controller.rb +9 -37
  15. data/examples/simulator_controller.rb +13 -22
  16. data/examples/ussd_controller.rb +41 -41
  17. data/examples/whatsapp_controller.rb +32 -125
  18. data/examples/whatsapp_media_examples.rb +68 -336
  19. data/examples/whatsapp_message_job.rb +5 -3
  20. data/flow_chat.gemspec +6 -2
  21. data/lib/flow_chat/base_processor.rb +79 -2
  22. data/lib/flow_chat/config.rb +31 -5
  23. data/lib/flow_chat/context.rb +13 -1
  24. data/lib/flow_chat/instrumentation/log_subscriber.rb +176 -0
  25. data/lib/flow_chat/instrumentation/metrics_collector.rb +197 -0
  26. data/lib/flow_chat/instrumentation/setup.rb +155 -0
  27. data/lib/flow_chat/instrumentation.rb +70 -0
  28. data/lib/flow_chat/prompt.rb +20 -20
  29. data/lib/flow_chat/session/cache_session_store.rb +73 -7
  30. data/lib/flow_chat/session/middleware.rb +130 -12
  31. data/lib/flow_chat/session/rails_session_store.rb +36 -1
  32. data/lib/flow_chat/simulator/controller.rb +8 -8
  33. data/lib/flow_chat/simulator/views/simulator.html.erb +5 -5
  34. data/lib/flow_chat/ussd/gateway/nalo.rb +31 -0
  35. data/lib/flow_chat/ussd/gateway/nsano.rb +36 -2
  36. data/lib/flow_chat/ussd/middleware/choice_mapper.rb +109 -0
  37. data/lib/flow_chat/ussd/middleware/executor.rb +24 -2
  38. data/lib/flow_chat/ussd/middleware/pagination.rb +87 -7
  39. data/lib/flow_chat/ussd/processor.rb +16 -4
  40. data/lib/flow_chat/ussd/renderer.rb +1 -1
  41. data/lib/flow_chat/version.rb +1 -1
  42. data/lib/flow_chat/whatsapp/client.rb +99 -12
  43. data/lib/flow_chat/whatsapp/configuration.rb +35 -4
  44. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +121 -34
  45. data/lib/flow_chat/whatsapp/middleware/executor.rb +24 -2
  46. data/lib/flow_chat/whatsapp/processor.rb +7 -1
  47. data/lib/flow_chat/whatsapp/renderer.rb +4 -9
  48. data/lib/flow_chat.rb +23 -0
  49. metadata +23 -12
  50. data/.travis.yml +0 -6
  51. data/app/controllers/demo_controller.rb +0 -101
  52. data/app/flow_chat/demo_restaurant_flow.rb +0 -889
  53. data/config/routes_demo.rb +0 -59
  54. data/examples/initializer.rb +0 -86
  55. data/examples/media_prompts_examples.rb +0 -27
  56. data/images/ussd_simulator.png +0 -0
  57. data/lib/flow_chat/ussd/middleware/resumable_session.rb +0 -39
data/README.md CHANGED
@@ -1,43 +1,33 @@
1
1
  # FlowChat
2
2
 
3
- FlowChat is a Rails framework designed for building sophisticated conversational workflows for both USSD (Unstructured Supplementary Service Data) systems and WhatsApp messaging. It provides an intuitive Ruby DSL for creating multi-step, menu-driven conversations with automatic session management, input validation, and flow control.
3
+ [![CI](https://github.com/radioactive-labs/flow_chat/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/radioactive-labs/flow_chat/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/flow_chat.svg)](https://badge.fury.io/rb/flow_chat)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%202.3.0-red.svg)](https://www.ruby-lang.org/)
7
+ [![Rails](https://img.shields.io/badge/rails-%3E%3D%206.0-red.svg)](https://rubyonrails.org/)
4
8
 
5
- **Key Features:**
6
- - ๐ŸŽฏ **Declarative Flow Definition** - Define conversation flows as Ruby classes
7
- - ๐Ÿ”„ **Automatic Session Management** - Persistent state across requests
8
- - โœ… **Input Validation & Transformation** - Built-in validation and data conversion
9
- - ๐ŸŒŠ **Middleware Architecture** - Flexible request processing pipeline
10
- - ๐Ÿ“ฑ **USSD Gateway Support** - Currently supports Nalo gateways
11
- - ๐Ÿ’ฌ **WhatsApp Integration** - Full WhatsApp Cloud API support with multiple processing modes and webhook signature validation
12
- - ๐Ÿ”ง **Reusable WhatsApp Client** - Standalone client for out-of-band messaging
13
- - ๐Ÿงช **Built-in Testing Tools** - Unified simulator for both USSD and WhatsApp testing
14
-
15
- ## Architecture Overview
16
-
17
- FlowChat uses a **request-per-interaction** model where each user input creates a new request. The framework maintains conversation state through session storage while processing each interaction through a middleware pipeline.
9
+ FlowChat is a Rails framework for building sophisticated conversational workflows for USSD and WhatsApp messaging. Define multi-step conversations as Ruby classes with automatic session management, input validation, and cross-platform compatibility.
18
10
 
19
- ```
20
- User Input โ†’ Gateway โ†’ Session โ†’ Pagination โ†’ Custom โ†’ Executor โ†’ Flow โ†’ Response
21
- โ†“
22
- Session Storage
23
- ```
11
+ ## Key Features
24
12
 
25
- **Middleware Pipeline:**
26
- - **Gateway**: Communication with providers (USSD: Nalo, WhatsApp: Cloud API)
27
- - **Session**: Load/save conversation state
28
- - **Pagination**: Split long responses into pages (USSD only)
29
- - **Custom**: Your application middleware (logging, auth, etc.)
30
- - **Executor**: Execute flow methods and handle interrupts
13
+ - ๐ŸŽฏ **Declarative Flow Definition** - Define conversations as Ruby classes
14
+ - ๐Ÿ”„ **Automatic Session Management** - Persistent state across requests
15
+ - โœ… **Input Validation & Transformation** - Built-in validation and data conversion
16
+ - ๐Ÿ“ฑ **USSD & WhatsApp Support** - Single codebase, multiple platforms
17
+ - ๐Ÿ’ฌ **Rich WhatsApp Features** - Interactive buttons, lists, media support
18
+ - ๐Ÿ”ง **Standalone WhatsApp Client** - Send messages outside of flows
19
+ - ๐Ÿ“Š **Built-in Instrumentation** - Monitoring and metrics out of the box
20
+ - ๐Ÿงช **Testing Tools** - Built-in simulator for development and testing
31
21
 
32
22
  ## Installation
33
23
 
34
- Add FlowChat to your Rails application's Gemfile:
24
+ Add to your Gemfile:
35
25
 
36
26
  ```ruby
37
27
  gem 'flow_chat'
38
28
  ```
39
29
 
40
- Then execute:
30
+ Then run:
41
31
 
42
32
  ```bash
43
33
  bundle install
@@ -45,30 +35,28 @@ bundle install
45
35
 
46
36
  ## Quick Start
47
37
 
48
- FlowChat supports both USSD and WhatsApp. Choose the platform that fits your needs:
38
+ ### USSD Example
49
39
 
50
- ### USSD Setup
51
-
52
- ### 1. Create Your First Flow
53
-
54
- Create a flow class in `app/flow_chat/welcome_flow.rb`:
40
+ Create a flow in `app/flow_chat/welcome_flow.rb`:
55
41
 
56
42
  ```ruby
57
43
  class WelcomeFlow < FlowChat::Flow
58
44
  def main_page
59
45
  name = app.screen(:name) do |prompt|
60
- prompt.ask "Welcome! What's your name?",
46
+ prompt.ask "What's your name?",
61
47
  transform: ->(input) { input.strip.titleize }
62
48
  end
63
49
 
64
- app.say "Hello, #{name}! Welcome to FlowChat."
50
+ language = app.screen(:language) do |prompt|
51
+ prompt.select "Choose language:", ["English", "French", "Spanish"]
52
+ end
53
+
54
+ app.say "Hello #{name}! Language set to #{language}."
65
55
  end
66
56
  end
67
57
  ```
68
58
 
69
- ### 2. Set Up the USSD Controller
70
-
71
- Create a controller to handle USSD requests:
59
+ Create a controller:
72
60
 
73
61
  ```ruby
74
62
  class UssdController < ApplicationController
@@ -85,193 +73,27 @@ class UssdController < ApplicationController
85
73
  end
86
74
  ```
87
75
 
88
- ### 3. Configure Routes
89
-
90
- Add the route to `config/routes.rb`:
76
+ Add route in `config/routes.rb`:
91
77
 
92
78
  ```ruby
93
- Rails.application.routes.draw do
94
79
  post 'ussd' => 'ussd#process_request'
95
- end
96
80
  ```
97
81
 
98
- ๐Ÿ’ก **Tip**: See [examples/ussd_controller.rb](examples/ussd_controller.rb) for a complete USSD controller example with payment flows, customer support, and custom middleware.
82
+ ### WhatsApp Example
99
83
 
100
- ### WhatsApp Setup
101
-
102
- ### 1. Configure WhatsApp Credentials
103
-
104
- FlowChat supports two ways to configure WhatsApp credentials:
105
-
106
- **Option A: Using Rails Credentials**
107
-
108
- Add your WhatsApp credentials to Rails credentials:
109
-
110
- ```bash
111
- rails credentials:edit
112
- ```
84
+ Configure credentials in `config/credentials.yml.enc`:
113
85
 
114
86
  ```yaml
115
87
  whatsapp:
116
88
  access_token: "your_access_token"
117
89
  phone_number_id: "your_phone_number_id"
118
90
  verify_token: "your_verify_token"
119
- app_id: "your_app_id"
120
91
  app_secret: "your_app_secret"
121
- business_account_id: "your_business_account_id"
122
- skip_signature_validation: false # Set to true only for development/testing
123
92
  ```
124
93
 
125
- **Option B: Using Environment Variables**
126
-
127
- Alternatively, you can use environment variables:
128
-
129
- ```bash
130
- # Add to your .env file or environment
131
- export WHATSAPP_ACCESS_TOKEN="your_access_token"
132
- export WHATSAPP_PHONE_NUMBER_ID="your_phone_number_id"
133
- export WHATSAPP_VERIFY_TOKEN="your_verify_token"
134
- export WHATSAPP_APP_ID="your_app_id"
135
- export WHATSAPP_APP_SECRET="your_app_secret"
136
- export WHATSAPP_BUSINESS_ACCOUNT_ID="your_business_account_id"
137
- export WHATSAPP_SKIP_SIGNATURE_VALIDATION="false" # Set to "true" only for development/testing
138
- ```
139
-
140
- FlowChat will automatically use Rails credentials first, falling back to environment variables if credentials are not available.
141
-
142
- **Option C: Per-Setup Configuration**
143
-
144
- For multi-tenant applications or when you need different WhatsApp accounts per endpoint:
94
+ Create a controller:
145
95
 
146
96
  ```ruby
147
- # Create custom configuration
148
- custom_config = FlowChat::Whatsapp::Configuration.new
149
- custom_config.access_token = "your_specific_access_token"
150
- custom_config.phone_number_id = "your_specific_phone_number_id"
151
- custom_config.verify_token = "your_specific_verify_token"
152
- custom_config.app_id = "your_specific_app_id"
153
- custom_config.app_secret = "your_specific_app_secret"
154
- custom_config.business_account_id = "your_specific_business_account_id"
155
- custom_config.skip_signature_validation = false # Security setting
156
-
157
- # Use in processor
158
- processor = FlowChat::Whatsapp::Processor.new(self) do |config|
159
- config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, custom_config
160
- config.use_session_store FlowChat::Session::CacheSessionStore
161
- end
162
- ```
163
-
164
- ๐Ÿ’ก **Tip**: See [examples/multi_tenant_whatsapp_controller.rb](examples/multi_tenant_whatsapp_controller.rb) for comprehensive multi-tenant and per-setup configuration examples.
165
-
166
- ### 2. Security Configuration
167
-
168
- FlowChat includes robust security features for WhatsApp webhook validation:
169
-
170
- **Webhook Signature Validation** (Recommended for Production)
171
-
172
- FlowChat automatically validates WhatsApp webhook signatures using your app secret:
173
-
174
- ```ruby
175
- # config/initializers/flowchat.rb
176
-
177
- # Global security configuration
178
- FlowChat::Config.simulator_secret = "your_secure_random_secret_for_simulator"
179
-
180
- # WhatsApp security is configured per-configuration
181
- # The app_secret from your WhatsApp configuration is used for webhook validation
182
- ```
183
-
184
- **Security Options:**
185
-
186
- ```ruby
187
- # Option 1: Full security (recommended for production)
188
- custom_config.app_secret = "your_whatsapp_app_secret" # Required for signature validation
189
- custom_config.skip_signature_validation = false # Default: enforce validation
190
-
191
- # Option 2: Disable validation (development/testing only)
192
- custom_config.app_secret = nil # Not required when disabled
193
- custom_config.skip_signature_validation = true # Explicitly disable validation
194
- ```
195
-
196
- โš ๏ธ **Security Warning**: Only disable signature validation in development/testing environments. Production environments should always validate webhook signatures using your WhatsApp app secret.
197
-
198
- **Simulator Authentication**
199
-
200
- The simulator mode requires authentication:
201
-
202
- ```ruby
203
- # config/initializers/flowchat.rb
204
- FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_simulator"
205
-
206
- # Or use a dedicated secret
207
- FlowChat::Config.simulator_secret = "your_secure_random_secret_here"
208
- ```
209
-
210
- The simulator uses HMAC-SHA256 signed cookies for authentication with 24-hour expiration.
211
-
212
- ๐Ÿ“š **For comprehensive security documentation, see [SECURITY.md](SECURITY.md)**
213
-
214
- ### 3. Choose Message Handling Mode
215
-
216
- FlowChat offers three WhatsApp message handling modes. Configure them in an initializer:
217
-
218
- **Create an initializer** `config/initializers/flowchat.rb`:
219
-
220
- ```ruby
221
- # config/initializers/flowchat.rb
222
-
223
- # Configure WhatsApp message handling mode
224
- FlowChat::Config.whatsapp.message_handling_mode = :inline # or :background, :simulator
225
- FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
226
- ```
227
-
228
- **Inline Mode (Default)** - Process messages synchronously:
229
- ```ruby
230
- # config/initializers/flowchat.rb
231
- FlowChat::Config.whatsapp.message_handling_mode = :inline
232
-
233
- # app/controllers/whatsapp_controller.rb
234
- class WhatsappController < ApplicationController
235
- skip_forgery_protection
236
-
237
- def webhook
238
- processor = FlowChat::Whatsapp::Processor.new(self) do |config|
239
- config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
240
- config.use_session_store FlowChat::Session::CacheSessionStore
241
- end
242
-
243
- processor.run WelcomeFlow, :main_page
244
- end
245
- end
246
- ```
247
-
248
- **Background Mode** - Process flows synchronously, send responses asynchronously:
249
- ```ruby
250
- # config/initializers/flowchat.rb
251
- FlowChat::Config.whatsapp.message_handling_mode = :background
252
- FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
253
-
254
- # app/controllers/whatsapp_controller.rb
255
- class WhatsappController < ApplicationController
256
- skip_forgery_protection
257
-
258
- def webhook
259
- processor = FlowChat::Whatsapp::Processor.new(self) do |config|
260
- config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
261
- config.use_session_store FlowChat::Session::CacheSessionStore
262
- end
263
-
264
- processor.run WelcomeFlow, :main_page
265
- end
266
- end
267
- ```
268
-
269
- **Simulator Mode** - Return response data instead of sending via WhatsApp API:
270
- ```ruby
271
- # config/initializers/flowchat.rb
272
- FlowChat::Config.whatsapp.message_handling_mode = :simulator
273
-
274
- # app/controllers/whatsapp_controller.rb
275
97
  class WhatsappController < ApplicationController
276
98
  skip_forgery_protection
277
99
 
@@ -286,1068 +108,102 @@ class WhatsappController < ApplicationController
286
108
  end
287
109
  ```
288
110
 
289
- ### 4. Add WhatsApp Route
111
+ Add route:
290
112
 
291
113
  ```ruby
292
- Rails.application.routes.draw do
293
114
  match '/whatsapp/webhook', to: 'whatsapp#webhook', via: [:get, :post]
294
- end
295
- ```
296
-
297
- ### 5. Enhanced Simulator Setup
298
-
299
- FlowChat provides a powerful built-in simulator for testing flows in both USSD and WhatsApp modes. The simulator allows you to test different endpoints on your local server without needing actual USSD or WhatsApp infrastructure.
300
-
301
- **Setup Simulator Controller:**
302
-
303
- ```ruby
304
- # app/controllers/simulator_controller.rb
305
- class SimulatorController < ApplicationController
306
- include FlowChat::Simulator::Controller
307
-
308
- def index
309
- flowchat_simulator
310
- end
311
-
312
- protected
313
-
314
- def configurations
315
- {
316
- ussd: {
317
- name: "USSD Integration",
318
- icon: "๐Ÿ“ฑ",
319
- processor_type: "ussd",
320
- provider: "nalo",
321
- endpoint: "/ussd",
322
- color: "#007bff"
323
- },
324
- whatsapp: {
325
- name: "WhatsApp Integration",
326
- icon: "๐Ÿ’ฌ",
327
- processor_type: "whatsapp",
328
- provider: "cloud_api",
329
- endpoint: "/whatsapp/webhook",
330
- color: "#25D366"
331
- },
332
- alternative_whatsapp: {
333
- name: "Alternative WhatsApp Endpoint",
334
- icon: "๐Ÿ”„",
335
- processor_type: "whatsapp",
336
- provider: "cloud_api",
337
- endpoint: "/alternative_whatsapp/webhook",
338
- color: "#17a2b8"
339
- }
340
- }
341
- end
342
-
343
- def default_config_key
344
- :local_whatsapp
345
- end
346
-
347
- def default_phone_number
348
- "+1234567890"
349
- end
350
-
351
- def default_contact_name
352
- "John Doe"
353
- end
354
- end
355
- ```
356
-
357
- **Add Simulator Route:**
358
-
359
- ```ruby
360
- # config/routes.rb
361
- Rails.application.routes.draw do
362
- get '/simulator' => 'simulator#index'
363
- # ... other routes
364
- end
365
- ```
366
-
367
- **Configure Simulator Security:**
368
-
369
- ```ruby
370
- # config/initializers/flowchat.rb
371
- FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_simulator"
372
- ```
373
-
374
- **Enable Simulator Mode in Controllers:**
375
-
376
- For controllers that should support simulator mode, enable it in the processor:
377
-
378
- ```ruby
379
- # app/controllers/whatsapp_controller.rb
380
- class WhatsappController < ApplicationController
381
- skip_forgery_protection
382
-
383
- def webhook
384
- processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: Rails.env.local?) do |config|
385
- config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
386
- config.use_session_store FlowChat::Session::CacheSessionStore
387
- end
388
-
389
- processor.run WelcomeFlow, :main_page
390
- end
391
- end
392
- ```
393
-
394
- This is enabled by default in `Rails.env.local?` (development and testing) environments.
395
-
396
- **Simulator Features:**
397
- - ๐Ÿ”„ **Endpoint Toggle** - Switch between different integration endpoints
398
- - ๐Ÿ“ฑ **USSD Mode** - Classic terminal simulation with pagination
399
- - ๐Ÿ’ฌ **WhatsApp Mode** - Full WhatsApp interface with interactive elements
400
- - ๐ŸŽจ **Modern UI** - Beautiful, responsive interface
401
- - ๐Ÿ“Š **Request Logging** - View HTTP requests and responses in real-time
402
- - ๐Ÿ”ง **Developer Tools** - Character counts, connection status, error handling
403
- - ๐Ÿ”’ **Secure Authentication** - HMAC-signed cookies with expiration
404
-
405
- Visit `http://localhost:3000/simulator` to access the simulator interface and test your local endpoints.
406
-
407
- ## ๐Ÿ”ง Reusable WhatsApp Client
408
-
409
- FlowChat provides a standalone WhatsApp client for out-of-band messaging:
410
-
411
- ```ruby
412
- # Initialize client
413
- config = FlowChat::Whatsapp::Configuration.from_credentials
414
- client = FlowChat::Whatsapp::Client.new(config)
415
-
416
- # Send text message
417
- client.send_text("+1234567890", "Hello, World!")
418
-
419
- # Send interactive buttons
420
- client.send_buttons(
421
- "+1234567890",
422
- "Choose an option:",
423
- [
424
- { id: 'option1', title: 'Option 1' },
425
- { id: 'option2', title: 'Option 2' }
426
- ]
427
- )
428
-
429
- # Send interactive list
430
- client.send_list(
431
- "+1234567890",
432
- "Select from menu:",
433
- [
434
- {
435
- title: "Services",
436
- rows: [
437
- { id: 'service1', title: 'Service 1', description: 'Description 1' },
438
- { id: 'service2', title: 'Service 2', description: 'Description 2' }
439
- ]
440
- }
441
- ]
442
- )
443
-
444
- # Handle media
445
- media_url = client.get_media_url("media_id_123")
446
- media_content = client.download_media("media_id_123")
447
- ```
448
-
449
- ### Out-of-Band Messaging Service Example
450
-
451
- ```ruby
452
- class NotificationService
453
- def initialize
454
- @config = FlowChat::Whatsapp::Configuration.from_credentials
455
- @client = FlowChat::Whatsapp::Client.new(@config)
456
- end
457
-
458
- def send_order_confirmation(phone_number, order_id, items, total)
459
- item_list = items.map { |item| "โ€ข #{item[:name]} x#{item[:quantity]}" }.join("\n")
460
-
461
- @client.send_buttons(
462
- phone_number,
463
- "โœ… Order Confirmed!\n\nOrder ##{order_id}\n\n#{item_list}\n\nTotal: $#{total}",
464
- [
465
- { id: 'track_order', title: '๐Ÿ“ฆ Track Order' },
466
- { id: 'contact_support', title: '๐Ÿ’ฌ Contact Support' }
467
- ]
468
- )
469
- end
470
-
471
- def send_appointment_reminder(phone_number, appointment)
472
- @client.send_buttons(
473
- phone_number,
474
- "๐Ÿฅ Appointment Reminder\n\n#{appointment[:service]} with #{appointment[:provider]}\n๐Ÿ“… #{appointment[:date]}\n๐Ÿ• #{appointment[:time]}",
475
- [
476
- { id: 'confirm', title: 'โœ… Confirm' },
477
- { id: 'reschedule', title: '๐Ÿ“… Reschedule' },
478
- { id: 'cancel', title: 'โŒ Cancel' }
479
- ]
480
- )
481
- end
482
- end
483
- ```
484
-
485
-
486
- ## Cross-Platform Compatibility
487
-
488
- FlowChat provides a unified API that works across both USSD and WhatsApp platforms, with graceful degradation for platform-specific features. Under the hood, FlowChat uses a unified prompt architecture that ensures consistent behavior across platforms while optimizing the user experience for each channel.
489
-
490
- ### Shared Features (Both USSD & WhatsApp)
491
- - โœ… `app.screen()` - Interactive screens with prompts
492
- - โœ… `app.say()` - Send messages to users
493
- - โœ… `prompt.ask()` - Text input collection
494
- - โœ… `prompt.select()` - Menu selection (renders as numbered list in USSD, interactive buttons/lists in WhatsApp)
495
- - โœ… `prompt.yes?()` - Yes/no questions
496
- - โœ… `app.phone_number` - User's phone number
497
- - โœ… `app.message_id` - Unique message identifier
498
- - โœ… `app.timestamp` - Message timestamp
499
-
500
- ### WhatsApp-Only Features
501
- - โœ… `app.contact_name` - WhatsApp contact name (returns `nil` in USSD)
502
- - โœ… `app.location` - Location sharing data (returns `nil` in USSD)
503
- - โœ… `app.media` - Media file attachments (returns `nil` in USSD)
504
- - โœ… Rich interactive elements (buttons, lists) automatically generated from `prompt.select()`
505
-
506
- This design allows you to write flows once and deploy them on both platforms, with WhatsApp users getting enhanced interactive features automatically.
507
-
508
- ## ๐Ÿ“ฑ Media Support
509
-
510
- FlowChat supports rich media attachments for enhanced conversational experiences. Media can be attached to `ask()` and `say()` prompts, with automatic cross-platform optimization.
511
-
512
- ### Supported Media Types
513
-
514
- - **๐Ÿ“ท Images** (`type: :image`) - Photos, screenshots, diagrams
515
- - **๐Ÿ“„ Documents** (`type: :document`) - PDFs, forms, receipts
516
- - **๐ŸŽฅ Videos** (`type: :video`) - Tutorials, demos, explanations
517
- - **๐ŸŽต Audio** (`type: :audio`) - Voice messages, recordings
518
- - **๐Ÿ˜Š Stickers** (`type: :sticker`) - Fun visual elements
519
-
520
- ### Basic Usage
521
-
522
- ```ruby
523
- class ProductFlow < FlowChat::Flow
524
- def main_page
525
- # โœ… Text input with context image
526
- feedback = app.screen(:feedback) do |prompt|
527
- prompt.ask "What do you think of our new product?",
528
- media: {
529
- type: :image,
530
- url: "https://cdn.example.com/products/new_product.jpg"
531
- }
532
- end
533
-
534
- # โœ… Send informational media
535
- app.say "Thanks for your feedback! Here's what's coming next:",
536
- media: {
537
- type: :video,
538
- url: "https://videos.example.com/roadmap.mp4"
539
- }
540
-
541
- # โœ… Document with filename
542
- app.say "Here's your receipt:",
543
- media: {
544
- type: :document,
545
- url: "https://api.example.com/receipt.pdf",
546
- filename: "receipt.pdf"
547
- }
548
- end
549
- end
550
- ```
551
-
552
- ### Media Hash Format
553
-
554
- ```ruby
555
- {
556
- type: :image, # Required: :image, :document, :audio, :video, :sticker
557
- url: "https://...", # Required: URL to the media file OR WhatsApp media ID
558
- filename: "doc.pdf" # Optional: Only for documents
559
- }
560
- ```
561
-
562
- ### Using WhatsApp Media IDs
563
-
564
- For better performance and to avoid external dependencies, you can upload files to WhatsApp and use the media ID:
565
-
566
- ```ruby
567
- # Upload a file first
568
- client = FlowChat::Whatsapp::Client.new(config)
569
- media_id = client.upload_media('path/to/image.jpg', 'image/jpeg')
570
-
571
- # Then use the media ID in your flow
572
- app.screen(:product_demo) do |prompt|
573
- prompt.ask "What do you think?",
574
- media: {
575
- type: :image,
576
- url: media_id # Use the media ID instead of URL
577
- }
578
- end
579
- ```
580
-
581
- ### Client Media Methods
582
-
583
- The WhatsApp client provides methods for uploading and sending media:
584
-
585
- ```ruby
586
- client = FlowChat::Whatsapp::Client.new(config)
587
-
588
- # Upload media and get media ID
589
- media_id = client.upload_media('image.jpg', 'image/jpeg')
590
- media_id = client.upload_media(file_io, 'image/jpeg', 'photo.jpg')
591
-
592
- # Send media directly
593
- client.send_image("+1234567890", "https://example.com/image.jpg", "Caption")
594
- client.send_image("+1234567890", media_id, "Caption")
595
-
596
- # Send document with MIME type and filename
597
- client.send_document("+1234567890", "https://example.com/doc.pdf", "Your receipt", "receipt.pdf", "application/pdf")
598
-
599
- # Send other media types
600
- client.send_video("+1234567890", "https://example.com/video.mp4", "Demo video", "video/mp4")
601
- client.send_audio("+1234567890", "https://example.com/audio.mp3", "audio/mpeg")
602
- client.send_sticker("+1234567890", "https://example.com/sticker.webp", "image/webp")
603
- ```
604
-
605
- ### Cross-Platform Behavior
606
-
607
- **WhatsApp Experience:**
608
- - Media is sent directly to the chat
609
- - Prompt text becomes the media caption
610
- - Rich, native messaging experience
611
-
612
- **USSD Experience:**
613
- - Media URL is included in text message
614
- - Graceful degradation with clear media indicators
615
- - Users can access media via the provided link
616
-
617
- ```ruby
618
- # This code works on both platforms:
619
- app.screen(:help) do |prompt|
620
- prompt.ask "Describe your issue:",
621
- media: {
622
- type: :image,
623
- url: "https://support.example.com/help_example.jpg"
624
- }
625
- end
626
- ```
627
-
628
- **WhatsApp Result:** Image sent with caption "Describe your issue:"
629
-
630
- **USSD Result:**
631
- ```
632
- Describe your issue:
633
-
634
- ๐Ÿ“ท Image: https://support.example.com/help_example.jpg
635
115
  ```
636
116
 
637
117
  ## Core Concepts
638
118
 
639
119
  ### Flows and Screens
640
120
 
641
- **Flows** are Ruby classes that define conversation logic. **Screens** represent individual interaction points where you collect user input.
121
+ **Flows** define conversation logic. **Screens** collect user input with automatic validation:
642
122
 
643
123
  ```ruby
644
124
  class RegistrationFlow < FlowChat::Flow
645
125
  def main_page
646
- # Each screen captures one piece of user input
647
- phone = app.screen(:phone) do |prompt|
648
- prompt.ask "Enter your phone number:",
649
- validate: ->(input) { "Invalid phone number" unless valid_phone?(input) }
650
- end
651
-
652
- age = app.screen(:age) do |prompt|
653
- prompt.ask "Enter your age:",
654
- convert: ->(input) { input.to_i },
655
- validate: ->(input) { "Must be 18 or older" unless input >= 18 }
126
+ email = app.screen(:email) do |prompt|
127
+ prompt.ask "Enter email:",
128
+ validate: ->(input) { "Invalid email" unless input.include?("@") },
129
+ transform: ->(input) { input.downcase.strip }
656
130
  end
657
131
 
658
- # Process the collected data
659
- create_user(phone: phone, age: age)
660
- app.say "Registration complete!"
661
- end
662
-
663
- private
664
-
665
- def valid_phone?(phone)
666
- phone.match?(/\A\+?[\d\s\-\(\)]+\z/)
667
- end
668
-
669
- def create_user(phone:, age:)
670
- # Your user creation logic here
671
- end
672
- end
673
- ```
674
-
675
- ### Input Validation and Transformation
676
-
677
- FlowChat provides powerful input processing capabilities:
678
-
679
- ```ruby
680
- app.screen(:email) do |prompt|
681
- prompt.ask "Enter your email:",
682
- # Transform input before validation
683
- transform: ->(input) { input.strip.downcase },
684
-
685
- # Validate the input
686
- validate: ->(input) {
687
- "Invalid email format" unless input.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
688
- },
689
-
690
- # Convert to final format
691
- convert: ->(input) { input }
692
- end
693
- ```
694
-
695
- ### Menu Selection
696
-
697
- Create selection menus with automatic validation:
698
-
699
- ```ruby
700
- # Array-based choices
701
- language = app.screen(:language) do |prompt|
702
- prompt.select "Choose your language:", ["English", "French", "Spanish"]
703
- end
704
-
705
- # Hash-based choices (keys are returned values)
706
- plan = app.screen(:plan) do |prompt|
707
- prompt.select "Choose a plan:", {
708
- "basic" => "Basic Plan ($10/month)",
709
- "premium" => "Premium Plan ($25/month)",
710
- "enterprise" => "Enterprise Plan ($100/month)"
711
- }
712
- end
713
- ```
714
-
715
- ### Yes/No Prompts
716
-
717
- Simplified boolean input collection:
718
-
719
- ```ruby
720
- confirmed = app.screen(:confirmation) do |prompt|
721
- prompt.yes? "Do you want to proceed with the payment?"
132
+ confirmed = app.screen(:confirm) do |prompt|
133
+ prompt.yes? "Create account for #{email}?"
722
134
  end
723
135
 
724
136
  if confirmed
725
- process_payment
726
- app.say "Payment processed successfully!"
727
- else
728
- app.say "Payment cancelled."
729
- end
730
- ```
731
-
732
- ## Advanced Features
733
-
734
- ### Session Management and Flow State
735
-
736
- FlowChat automatically manages session state across requests. Each screen's result is cached, so users can navigate back and forth without losing data:
737
-
738
- ```ruby
739
- class OrderFlow < FlowChat::Flow
740
- def main_page
741
- # These values persist across requests
742
- product = app.screen(:product) { |p| p.select "Choose product:", products }
743
- quantity = app.screen(:quantity) { |p| p.ask "Quantity:", convert: :to_i }
744
-
745
- # Show summary
746
- total = calculate_total(product, quantity)
747
- confirmed = app.screen(:confirm) do |prompt|
748
- prompt.yes? "Order #{quantity}x #{product} for $#{total}. Confirm?"
749
- end
750
-
751
- if confirmed
752
- process_order(product, quantity)
753
- app.say "Order placed successfully!"
137
+ create_account(email)
138
+ app.say "Account created successfully!"
754
139
  else
755
- app.say "Order cancelled."
756
- end
757
- end
758
- end
759
- ```
760
-
761
- ### Error Handling
762
-
763
- Handle validation errors gracefully:
764
-
765
- ```ruby
766
- app.screen(:credit_card) do |prompt|
767
- prompt.ask "Enter credit card number:",
768
- validate: ->(input) {
769
- return "Card number must be 16 digits" unless input.length == 16
770
- return "Invalid card number" unless luhn_valid?(input)
771
- nil # Return nil for valid input
772
- }
773
- end
774
- ```
775
-
776
- ### Validation Error Display
777
-
778
- Configure how validation errors are displayed to users:
779
-
780
- ```ruby
781
- # config/initializers/flowchat.rb
782
-
783
- # Default behavior: combine error with original message
784
- FlowChat::Config.combine_validation_error_with_message = true
785
- # User sees: "Card number must be 16 digits\n\nEnter credit card number:"
786
-
787
- # Show only the error message
788
- FlowChat::Config.combine_validation_error_with_message = false
789
- # User sees: "Card number must be 16 digits"
790
- ```
791
-
792
- **Use cases for each approach:**
793
-
794
- - **Combined (default)**: Better for first-time users who need context about what they're entering
795
- - **Error only**: Cleaner UX for experienced users, reduces message length for USSD character limits
796
-
797
- **Example with both approaches:**
798
-
799
- ```ruby
800
- # This validation code works the same way regardless of config
801
- age = app.screen(:age) do |prompt|
802
- prompt.ask "How old are you?",
803
- convert: ->(input) { input.to_i },
804
- validate: ->(input) { "You must be at least 18 years old" unless input >= 18 }
805
- end
806
-
807
- # With combine_validation_error_with_message = true (default):
808
- # "You must be at least 18 years old
809
- #
810
- # How old are you?"
811
-
812
- # With combine_validation_error_with_message = false:
813
- # "You must be at least 18 years old"
814
- ```
815
-
816
- ### Background Job Support
817
-
818
- For high-volume WhatsApp applications, use background response delivery:
819
-
820
- ```ruby
821
- # app/jobs/whatsapp_message_job.rb
822
- class WhatsappMessageJob < ApplicationJob
823
- include FlowChat::Whatsapp::SendJobSupport
824
-
825
- def perform(send_data)
826
- perform_whatsapp_send(send_data)
827
- end
828
- end
829
-
830
- # config/initializers/flowchat.rb
831
- FlowChat::Config.whatsapp.message_handling_mode = :background
832
- FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
833
-
834
- # config/application.rb
835
- config.active_job.queue_adapter = :sidekiq
836
- ```
837
-
838
- **The `SendJobSupport` module provides:**
839
- - โœ… **Automatic config resolution** - Resolves named configurations automatically
840
- - โœ… **Response delivery** - Handles sending responses to WhatsApp
841
- - โœ… **Error handling** - Comprehensive error handling with user notifications
842
- - โœ… **Retry logic** - Built-in exponential backoff retry
843
- - โœ… **Extensible** - Override methods for custom behavior
844
-
845
- **How it works:**
846
- 1. **Controller receives webhook** - WhatsApp message arrives
847
- 2. **Flow processes synchronously** - Maintains controller context and session state
848
- 3. **Response queued for delivery** - Only the sending is moved to background
849
- 4. **Job sends response** - Background job handles API call to WhatsApp
850
-
851
- **Advanced job with custom callbacks:**
852
-
853
- ```ruby
854
- class AdvancedWhatsappMessageJob < ApplicationJob
855
- include FlowChat::Whatsapp::SendJobSupport
856
-
857
- def perform(send_data)
858
- perform_whatsapp_send(send_data)
859
- end
860
-
861
- private
862
-
863
- # Override for custom success handling
864
- def on_whatsapp_send_success(send_data, result)
865
- Rails.logger.info "Successfully sent WhatsApp message to #{send_data[:msisdn]}"
866
- UserEngagementTracker.track_message_sent(phone: send_data[:msisdn])
867
- end
868
-
869
- # Override for custom error handling
870
- def on_whatsapp_send_error(error, send_data)
871
- ErrorTracker.notify(error, user_phone: send_data[:msisdn])
872
- end
873
- end
874
- ```
875
-
876
- ๐Ÿ’ก **See [examples/whatsapp_message_job.rb](examples/whatsapp_message_job.rb) for complete job implementation examples.**
877
-
878
- ### Middleware Configuration
879
-
880
- FlowChat uses a **middleware architecture** to process USSD requests through a configurable pipeline. Each request flows through multiple middleware layers in a specific order.
881
-
882
- #### Default Middleware Stack
883
-
884
- When you run a flow, FlowChat automatically builds this middleware stack:
885
-
886
- ```
887
- User Input โ†’ Gateway โ†’ Session โ†’ Pagination โ†’ Custom Middleware โ†’ Executor โ†’ Flow
888
- ```
889
-
890
- 1. **Gateway Middleware** - Handles USSD provider communication (Nalo)
891
- 2. **Session Middleware** - Manages session storage and retrieval
892
- 3. **Pagination Middleware** - Automatically splits long responses across pages
893
- 4. **Custom Middleware** - Your application-specific middleware (optional)
894
- 5. **Executor Middleware** - Executes the actual flow logic
895
-
896
- #### Basic Configuration
897
-
898
- ```ruby
899
- processor = FlowChat::Ussd::Processor.new(self) do |config|
900
- # Gateway configuration (required)
901
- config.use_gateway FlowChat::Ussd::Gateway::Nalo
902
-
903
- # Session storage (required)
904
- config.use_session_store FlowChat::Session::CacheSessionStore
905
-
906
- # Add custom middleware (optional)
907
- config.use_middleware MyLoggingMiddleware
908
-
909
- # Enable resumable sessions (optional)
910
- config.use_resumable_sessions
911
- end
912
- ```
913
-
914
- #### Runtime Middleware Modification
915
-
916
- You can modify the middleware stack at runtime for advanced use cases:
917
-
918
- ```ruby
919
- processor.run(MyFlow, :main_page) do |stack|
920
- # Add authentication middleware
921
- stack.use AuthenticationMiddleware
922
-
923
- # Insert rate limiting before execution
924
- stack.insert_before FlowChat::Ussd::Middleware::Executor, RateLimitMiddleware
925
-
926
- # Add logging after gateway
927
- stack.insert_after gateway, RequestLoggingMiddleware
928
- end
929
- ```
930
-
931
- #### Built-in Middleware
932
-
933
- **Pagination Middleware** automatically handles responses longer than 182 characters (configurable):
934
-
935
- ```ruby
936
- # Configure pagination behavior
937
- FlowChat::Config.ussd.pagination_page_size = 140 # Default: 140 characters
938
- FlowChat::Config.ussd.pagination_next_option = "#" # Default: "#"
939
- FlowChat::Config.ussd.pagination_next_text = "More" # Default: "More"
940
- FlowChat::Config.ussd.pagination_back_option = "0" # Default: "0"
941
- FlowChat::Config.ussd.pagination_back_text = "Back" # Default: "Back"
942
- ```
943
-
944
- **Resumable Sessions** allow users to continue interrupted conversations:
945
-
946
- ```ruby
947
- processor = FlowChat::Ussd::Processor.new(self) do |config|
948
- config.use_gateway FlowChat::Ussd::Gateway::Nalo
949
- config.use_session_store FlowChat::Session::CacheSessionStore
950
- config.use_resumable_sessions # Enable resumable sessions
951
- end
952
- ```
953
-
954
- #### Creating Custom Middleware
955
-
956
- ```ruby
957
- class LoggingMiddleware
958
- def initialize(app)
959
- @app = app
960
- end
961
-
962
- def call(context)
963
- Rails.logger.info "Processing USSD request: #{context.input}"
964
-
965
- # Call the next middleware in the stack
966
- result = @app.call(context)
967
-
968
- Rails.logger.info "Response: #{result[1]}"
969
- result
970
- end
971
- end
972
-
973
- # Use your custom middleware
974
- processor = FlowChat::Ussd::Processor.new(self) do |config|
975
- config.use_gateway FlowChat::Ussd::Gateway::Nalo
976
- config.use_session_store FlowChat::Session::CacheSessionStore
977
- config.use_middleware LoggingMiddleware
978
- end
979
- ```
980
-
981
- ### Multiple Gateways
982
-
983
- FlowChat supports multiple USSD gateways:
984
-
985
- ```ruby
986
- # Nalo Solutions Gateway
987
- config.use_gateway FlowChat::Ussd::Gateway::Nalo
988
- ```
989
-
990
- ## Testing
991
-
992
- FlowChat provides comprehensive testing capabilities for both USSD and WhatsApp flows. This section covers all testing approaches from simple unit tests to complex integration scenarios.
993
-
994
- ### Testing Approaches Overview
995
-
996
- FlowChat supports two main testing strategies:
997
-
998
- **๐ŸŽฏ Option 1: Simulator Mode (Recommended)**
999
- - Bypasses WhatsApp API entirely
1000
- - No webhook signature validation required
1001
- - Responses returned as JSON instead of sent to WhatsApp
1002
- - Perfect for unit and integration testing
1003
- - Works with built-in web simulator interface
1004
-
1005
- **๐Ÿ”’ Option 2: Skip Signature Validation**
1006
- - Tests real webhook endpoints without security complexity
1007
- - Useful for staging environments
1008
- - Set `skip_signature_validation = true`
1009
-
1010
- ### Environment-Specific Testing Configuration
1011
-
1012
- Configure testing behavior per environment:
1013
-
1014
- ```ruby
1015
- # config/initializers/flowchat.rb
1016
- case Rails.env
1017
- when 'development'
1018
- # Enable simulator mode for testing
1019
- FlowChat::Config.whatsapp.message_handling_mode = :simulator
1020
- FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_dev"
1021
-
1022
- when 'test'
1023
- # Enable simulator mode for automated testing
1024
- FlowChat::Config.whatsapp.message_handling_mode = :simulator
1025
- FlowChat::Config.simulator_secret = "test_secret"
1026
-
1027
- when 'staging'
1028
- # Use inline mode but allow simulator for testing
1029
- FlowChat::Config.whatsapp.message_handling_mode = :inline
1030
- FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
1031
-
1032
- when 'production'
1033
- # Background processing in production, no simulator
1034
- FlowChat::Config.whatsapp.message_handling_mode = :background
1035
- FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
1036
- FlowChat::Config.simulator_secret = nil
1037
- end
1038
- ```
1039
-
1040
- ### Unit Testing Flows
1041
-
1042
- Test your flows in isolation using the provided test helpers:
1043
-
1044
- ```ruby
1045
- require 'test_helper'
1046
-
1047
- class WelcomeFlowTest < Minitest::Test
1048
- def setup
1049
- @context = FlowChat::Context.new
1050
- @context.session = create_test_session_store
1051
- end
1052
-
1053
- def test_welcome_flow_with_name
1054
- @context.input = "John Doe"
1055
- app = FlowChat::Ussd::App.new(@context)
1056
-
1057
- error = assert_raises(FlowChat::Interrupt::Terminate) do
1058
- flow = WelcomeFlow.new(app)
1059
- flow.main_page
140
+ app.say "Registration cancelled."
1060
141
  end
1061
-
1062
- assert_equal "Hello, John Doe! Welcome to FlowChat.", error.prompt
1063
142
  end
1064
143
  end
1065
144
  ```
1066
145
 
1067
- ### Integration Testing
1068
-
1069
- #### Simulator Mode Testing
1070
-
1071
- ```ruby
1072
- test "complete flow via simulator" do
1073
- # Generate valid simulator cookie for authentication
1074
- valid_cookie = generate_simulator_cookie
1075
-
1076
- webhook_payload = {
1077
- entry: [{ changes: [{ value: { messages: [{ from: "1234567890", text: { body: "Hello" }, type: "text" }] } }] }],
1078
- simulator_mode: true
1079
- }
1080
-
1081
- post "/whatsapp/webhook", params: webhook_payload, cookies: { flowchat_simulator: valid_cookie }
1082
- assert_response :success
1083
- end
1084
- ```
1085
-
1086
- #### Testing with Skipped Validation
1087
-
1088
- ```ruby
1089
- test "webhook processing with skipped validation" do
1090
- config = FlowChat::Whatsapp::Configuration.new
1091
- config.skip_signature_validation = true # Skip for testing
1092
-
1093
- # No signature required when validation is skipped
1094
- post "/whatsapp/webhook", params: webhook_payload.to_json
1095
- assert_response :success
1096
- end
1097
- ```
1098
-
1099
- ### Test Helper Methods
1100
-
1101
- ```ruby
1102
- private
146
+ ### Input Methods
1103
147
 
1104
- def generate_webhook_signature(payload_json, secret = @app_secret)
1105
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, payload_json)
1106
- end
1107
-
1108
- def generate_simulator_cookie(secret = FlowChat::Config.simulator_secret)
1109
- timestamp = Time.now.to_i
1110
- message = "simulator:#{timestamp}"
1111
- signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, message)
1112
- "#{timestamp}:#{signature}"
1113
- end
1114
- ```
148
+ - **`prompt.ask()`** - Free-form input with optional validation
149
+ - **`prompt.select()`** - Force selection from predefined options
150
+ - **`prompt.yes?()`** - Simple yes/no questions
1115
151
 
1116
- ## Configuration Reference
152
+ ## WhatsApp Client
1117
153
 
1118
- ### Framework Configuration
154
+ Send messages outside of flows:
1119
155
 
1120
156
  ```ruby
1121
- # config/initializers/flowchat.rb
1122
-
1123
- # Core configuration
1124
- FlowChat::Config.logger = Rails.logger
1125
- FlowChat::Config.cache = Rails.cache
1126
- FlowChat::Config.simulator_secret = "your_secure_secret_here"
1127
-
1128
- # Validation error display behavior
1129
- # When true (default), validation errors are combined with the original message.
1130
- # When false, only the validation error message is shown to the user.
1131
- FlowChat::Config.combine_validation_error_with_message = true
1132
-
1133
- # USSD configuration
1134
- FlowChat::Config.ussd.pagination_page_size = 140
1135
- FlowChat::Config.ussd.pagination_next_option = "#"
1136
- FlowChat::Config.ussd.pagination_next_text = "More"
1137
- FlowChat::Config.ussd.pagination_back_option = "0"
1138
- FlowChat::Config.ussd.pagination_back_text = "Back"
1139
- FlowChat::Config.ussd.resumable_sessions_enabled = true
1140
- FlowChat::Config.ussd.resumable_sessions_timeout_seconds = 300
1141
-
1142
- # WhatsApp configuration
1143
- FlowChat::Config.whatsapp.message_handling_mode = :inline # :inline, :background, :simulator
1144
- FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
1145
- ```
1146
-
1147
- ### WhatsApp Security Configuration
1148
-
1149
- ```ruby
1150
- # Per-configuration security settings
1151
- config = FlowChat::Whatsapp::Configuration.new
1152
- config.app_secret = "your_whatsapp_app_secret" # Required for signature validation
1153
- config.skip_signature_validation = false # Default: false (enforce validation)
1154
-
1155
- # Security modes:
1156
- # 1. Full security (production)
1157
- config.app_secret = "secret"
1158
- config.skip_signature_validation = false
1159
-
1160
- # 2. Development mode (disable validation)
1161
- config.app_secret = nil
1162
- config.skip_signature_validation = true
1163
- ```
1164
-
1165
- ## Best Practices
1166
-
1167
- ### 1. Security Best Practices
1168
-
1169
- **Production Security Checklist:**
1170
-
1171
- โœ… **Always configure app_secret** for webhook validation
1172
- ```ruby
1173
- config.app_secret = "your_whatsapp_app_secret" # Never leave empty in production
1174
- config.skip_signature_validation = false # Never disable in production
1175
- ```
1176
-
1177
- โœ… **Use secure simulator secrets**
1178
- ```ruby
1179
- # Use Rails secret_key_base + suffix for uniqueness
1180
- FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_simulator"
1181
-
1182
- # Or use environment variables
1183
- FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
1184
- ```
1185
-
1186
- โœ… **Environment-specific configuration**
1187
- ```ruby
1188
- # Different security levels per environment
1189
- if Rails.env.production?
1190
- config.skip_signature_validation = false # Enforce validation
1191
- else
1192
- config.skip_signature_validation = true # Allow for development
1193
- end
1194
- ```
1195
-
1196
- โœ… **Enable simulator only when needed**
1197
- ```ruby
1198
- # Only enable simulator in development/staging (default)
1199
- enable_simulator = Rails.env.development? || Rails.env.staging?
1200
- processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: enable_simulator)
1201
- ```
1202
-
1203
- ### 5. Choose the Right WhatsApp Mode
1204
-
1205
- Configure the appropriate mode based on your environment and requirements:
1206
-
1207
- ```ruby
1208
- # config/initializers/flowchat.rb
1209
-
1210
- # Development/Testing - use simulator mode with security
1211
- if Rails.env.development? || Rails.env.test?
1212
- FlowChat::Config.whatsapp.message_handling_mode = :simulator
1213
- FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_dev"
1214
- end
1215
-
1216
- # Staging - use inline mode with full security
1217
- if Rails.env.staging?
1218
- FlowChat::Config.whatsapp.message_handling_mode = :inline
1219
- FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
1220
- end
1221
-
1222
- # Production - use background mode with full security
1223
- if Rails.env.production?
1224
- FlowChat::Config.whatsapp.message_handling_mode = :background
1225
- FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
1226
- FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
1227
- end
1228
- ```
1229
-
1230
- ### 6. Error Handling Best Practices
1231
-
1232
- Handle security and configuration errors gracefully:
1233
-
1234
- ```ruby
1235
- def webhook
1236
- begin
1237
- processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: Rails.env.development?) do |config|
1238
- config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
1239
- config.use_session_store FlowChat::Session::CacheSessionStore
1240
- end
157
+ config = FlowChat::Whatsapp::Configuration.from_credentials
158
+ client = FlowChat::Whatsapp::Client.new(config)
1241
159
 
1242
- processor.run WelcomeFlow, :main_page
1243
-
1244
- rescue FlowChat::Whatsapp::ConfigurationError => e
1245
- Rails.logger.error "WhatsApp configuration error: #{e.message}"
1246
- head :internal_server_error
1247
-
1248
- rescue => e
1249
- Rails.logger.error "Unexpected error processing WhatsApp webhook: #{e.message}"
1250
- head :internal_server_error
1251
- end
1252
- end
160
+ # Send messages
161
+ client.send_text("+1234567890", "Hello!")
162
+ client.send_buttons("+1234567890", "Choose:", [
163
+ { id: 'option1', title: 'Option 1' },
164
+ { id: 'option2', title: 'Option 2' }
165
+ ])
1253
166
  ```
1254
167
 
1255
- ## Appendix
1256
-
1257
- ### Context Variables Reference
1258
-
1259
- FlowChat provides a rich set of context variables that are available throughout your flows. These variables are automatically populated based on the gateway and message type.
1260
-
1261
- #### Core Context Variables
1262
-
1263
- **Request Variables**
1264
-
1265
- | Variable | Description |
1266
- |----------|-------------|
1267
- | `context.input` | Current user input |
1268
- | `context["request.id"]` | Unique request ID |
1269
- | `context["request.timestamp"]` | Request timestamp |
1270
- | `context["request.msisdn"]` | User's phone number |
1271
- | `context["request.gateway"]` | Current gateway (`:whatsapp_cloud_api`, `:ussd_nalo`) |
1272
- | `context["enable_simulator"]` | Whether simulator mode is enabled for this request |
1273
- | `context["simulator_mode"]` | Whether simulator mode is active |
1274
-
1275
- **Flow Variables**
1276
-
1277
- | Variable | Description |
1278
- |----------|-------------|
1279
- | `context["flow.name"]` | Current flow name |
1280
- | `context["flow.class"]` | Current flow class |
1281
- | `context["flow.action"]` | Current flow action/method |
1282
-
1283
- **Session Variables**
1284
-
1285
- | Variable | Description |
1286
- |----------|-------------|
1287
- | `context["session.id"]` | Current session ID |
1288
- | `context["session.store"]` | Session data store |
1289
-
1290
- **Controller Variables**
1291
-
1292
- | Variable | Description |
1293
- |----------|-------------|
1294
- | `context.controller` | Current controller instance |
168
+ ## Testing Simulator
1295
169
 
1296
- #### WhatsApp-Specific Variables
170
+ FlowChat includes a powerful built-in simulator with a modern web interface for testing both USSD and WhatsApp flows:
1297
171
 
1298
- | Variable | Description |
1299
- |----------|-------------|
1300
- | `context["whatsapp.message_result"]` | Result of last message send |
1301
- | `context["whatsapp.message_id"]` | WhatsApp message ID |
1302
- | `context["whatsapp.contact_name"]` | WhatsApp contact name |
1303
- | `context["whatsapp.location"]` | Location data if shared |
1304
- | `context["whatsapp.media"]` | Media data if attached |
172
+ ![FlowChat Simulator](docs/images/simulator.png)
1305
173
 
1306
- #### USSD-Specific Variables
174
+ Features:
175
+ - ๐Ÿ“ฑ **Visual Interface** - Phone-like display with real conversation flow
176
+ - ๐Ÿ”„ **Environment Switching** - Toggle between USSD and WhatsApp modes
177
+ - ๐Ÿ“Š **Request Logging** - Real-time HTTP request/response monitoring
178
+ - ๐ŸŽฏ **Interactive Testing** - Test flows with character counting and validation
179
+ - ๐Ÿ› ๏ธ **Developer Tools** - Session management and connection status
1307
180
 
1308
- | Variable | Description |
1309
- |----------|-------------|
1310
- | `context["ussd.request"]` | Original USSD request |
1311
- | `context["ussd.response"]` | USSD response object |
1312
- | `context["ussd.pagination"]` | Pagination data for long messages |
181
+ See the [Testing Guide](docs/testing.md) for complete setup instructions and testing strategies.
1313
182
 
1314
- #### Session Data Variables
1315
183
 
1316
- | Variable | Description |
1317
- |----------|-------------|
1318
- | `context["$started_at$"]` | When the conversation started |
184
+ ## Documentation
1319
185
 
1320
- #### Usage Notes
186
+ - **[WhatsApp Setup](docs/whatsapp-setup.md)** - Comprehensive WhatsApp configuration
187
+ - **[USSD Setup](docs/ussd-setup.md)** - USSD gateway configuration and examples
188
+ - **[Flow Development](docs/flows.md)** - Advanced flow patterns and techniques
189
+ - **[Session Management](docs/sessions.md)** - Session architecture and configuration
190
+ - **[Media Support](docs/media.md)** - Rich media handling for WhatsApp
191
+ - **[Testing Guide](docs/testing.md)** - Complete testing strategies
192
+ - **[Configuration Reference](docs/configuration.md)** - All configuration options
193
+ - **[Instrumentation](docs/instrumentation.md)** - Monitoring and metrics
194
+ - **[Security](docs/security.md)** - Security best practices
195
+ - **[Examples](examples/)** - Complete working examples
1321
196
 
1322
- 1. **Access Methods:**
1323
- ```ruby
1324
- # Direct access
1325
- context.input
1326
- context["request.msisdn"]
1327
-
1328
- # Through app object in flows
1329
- app.phone_number
1330
- app.contact_name
1331
- app.message_id
1332
- ```
197
+ ## Examples
1333
198
 
1334
- 2. **Gateway-Specific Variables:**
1335
- - WhatsApp variables are only available when using WhatsApp gateway
1336
- - USSD variables are only available when using USSD gateway
1337
- - Core variables are available across all gateways
199
+ See the [examples directory](examples/) for complete implementations:
1338
200
 
1339
- 3. **Session Persistence:**
1340
- - Session data persists across requests
1341
- - WhatsApp sessions expire after 7 days
1342
- - USSD sessions expire after 1 hour
1343
- - Default session TTL is 1 day
201
+ - **[USSD Controller](examples/ussd_controller.rb)** - Full USSD implementation
202
+ - **[WhatsApp Controller](examples/whatsapp_controller.rb)** - Basic WhatsApp setup
203
+ - **[Multi-tenant WhatsApp](examples/multi_tenant_whatsapp_controller.rb)** - Advanced WhatsApp
204
+ - **[Background Jobs](examples/whatsapp_message_job.rb)** - Async message processing
205
+ - **[Simulator Controller](examples/simulator_controller.rb)** - Testing setup
1344
206
 
1345
- 4. **Security:**
1346
- - Webhook signatures are validated for WhatsApp requests
1347
- - Simulator mode requires valid simulator cookie
1348
- - Session data is encrypted at rest
207
+ ## License
1349
208
 
1350
- 5. **Flow Control:**
1351
- - Context variables can be used to control flow logic
1352
- - Session data can be used to maintain state
1353
- - Request data can be used for validation
209
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).