flow_chat 0.4.1 โ 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +418 -295
- data/SECURITY.md +365 -0
- data/examples/initializer.rb +56 -1
- data/examples/media_prompts_examples.rb +1 -2
- data/examples/multi_tenant_whatsapp_controller.rb +61 -57
- data/examples/simulator_controller.rb +95 -0
- data/examples/ussd_controller.rb +17 -11
- data/examples/whatsapp_controller.rb +103 -14
- data/examples/whatsapp_media_examples.rb +78 -80
- data/examples/whatsapp_message_job.rb +3 -3
- data/lib/flow_chat/base_processor.rb +3 -2
- data/lib/flow_chat/config.rb +6 -3
- data/lib/flow_chat/session/cache_session_store.rb +5 -5
- data/lib/flow_chat/simulator/controller.rb +34 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +287 -12
- data/lib/flow_chat/ussd/gateway/nsano.rb +1 -1
- data/lib/flow_chat/ussd/processor.rb +1 -1
- data/lib/flow_chat/ussd/prompt.rb +13 -13
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/app.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +44 -50
- data/lib/flow_chat/whatsapp/configuration.rb +21 -20
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +129 -19
- data/lib/flow_chat/whatsapp/middleware/executor.rb +1 -1
- data/lib/flow_chat/whatsapp/processor.rb +1 -1
- data/lib/flow_chat/whatsapp/prompt.rb +27 -31
- data/lib/flow_chat/whatsapp/send_job_support.rb +7 -7
- data/lib/flow_chat/whatsapp/template_manager.rb +10 -10
- metadata +4 -2
data/README.md
CHANGED
@@ -8,7 +8,7 @@ FlowChat is a Rails framework designed for building sophisticated conversational
|
|
8
8
|
- โ
**Input Validation & Transformation** - Built-in validation and data conversion
|
9
9
|
- ๐ **Middleware Architecture** - Flexible request processing pipeline
|
10
10
|
- ๐ฑ **USSD Gateway Support** - Currently supports Nalo gateways
|
11
|
-
- ๐ฌ **WhatsApp Integration** - Full WhatsApp Cloud API support with multiple processing modes
|
11
|
+
- ๐ฌ **WhatsApp Integration** - Full WhatsApp Cloud API support with multiple processing modes and webhook signature validation
|
12
12
|
- ๐ง **Reusable WhatsApp Client** - Standalone client for out-of-band messaging
|
13
13
|
- ๐งช **Built-in Testing Tools** - Unified simulator for both USSD and WhatsApp testing
|
14
14
|
|
@@ -118,8 +118,8 @@ whatsapp:
|
|
118
118
|
verify_token: "your_verify_token"
|
119
119
|
app_id: "your_app_id"
|
120
120
|
app_secret: "your_app_secret"
|
121
|
-
webhook_url: "your_webhook_url"
|
122
121
|
business_account_id: "your_business_account_id"
|
122
|
+
skip_signature_validation: false # Set to true only for development/testing
|
123
123
|
```
|
124
124
|
|
125
125
|
**Option B: Using Environment Variables**
|
@@ -133,8 +133,8 @@ export WHATSAPP_PHONE_NUMBER_ID="your_phone_number_id"
|
|
133
133
|
export WHATSAPP_VERIFY_TOKEN="your_verify_token"
|
134
134
|
export WHATSAPP_APP_ID="your_app_id"
|
135
135
|
export WHATSAPP_APP_SECRET="your_app_secret"
|
136
|
-
export WHATSAPP_WEBHOOK_URL="your_webhook_url"
|
137
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
138
|
```
|
139
139
|
|
140
140
|
FlowChat will automatically use Rails credentials first, falling back to environment variables if credentials are not available.
|
@@ -152,6 +152,7 @@ custom_config.verify_token = "your_specific_verify_token"
|
|
152
152
|
custom_config.app_id = "your_specific_app_id"
|
153
153
|
custom_config.app_secret = "your_specific_app_secret"
|
154
154
|
custom_config.business_account_id = "your_specific_business_account_id"
|
155
|
+
custom_config.skip_signature_validation = false # Security setting
|
155
156
|
|
156
157
|
# Use in processor
|
157
158
|
processor = FlowChat::Whatsapp::Processor.new(self) do |config|
|
@@ -162,7 +163,55 @@ end
|
|
162
163
|
|
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.
|
164
165
|
|
165
|
-
### 2.
|
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
|
166
215
|
|
167
216
|
FlowChat offers three WhatsApp message handling modes. Configure them in an initializer:
|
168
217
|
|
@@ -237,9 +286,7 @@ class WhatsappController < ApplicationController
|
|
237
286
|
end
|
238
287
|
```
|
239
288
|
|
240
|
-
|
241
|
-
|
242
|
-
### 3. Add WhatsApp Route
|
289
|
+
### 4. Add WhatsApp Route
|
243
290
|
|
244
291
|
```ruby
|
245
292
|
Rails.application.routes.draw do
|
@@ -247,117 +294,115 @@ Rails.application.routes.draw do
|
|
247
294
|
end
|
248
295
|
```
|
249
296
|
|
250
|
-
###
|
251
|
-
|
252
|
-
The same flow works for both USSD and WhatsApp, but WhatsApp provides additional data and better interactive features:
|
297
|
+
### 5. Enhanced Simulator Setup
|
253
298
|
|
254
|
-
|
255
|
-
class WelcomeFlow < FlowChat::Flow
|
256
|
-
def main_page
|
257
|
-
# Access WhatsApp-specific data
|
258
|
-
Rails.logger.info "Contact: #{app.contact_name}, Phone: #{app.phone_number}"
|
259
|
-
Rails.logger.info "Message ID: #{app.message_id}, Timestamp: #{app.timestamp}"
|
260
|
-
|
261
|
-
# Handle location sharing
|
262
|
-
if app.location
|
263
|
-
app.say "Thanks for sharing your location! We see you're at #{app.location['latitude']}, #{app.location['longitude']}"
|
264
|
-
return
|
265
|
-
end
|
266
|
-
|
267
|
-
# Handle media messages
|
268
|
-
if app.media
|
269
|
-
app.say "Thanks for the #{app.media['type']} file! We received: #{app.media['id']}"
|
270
|
-
return
|
271
|
-
end
|
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.
|
272
300
|
|
273
|
-
|
274
|
-
prompt.ask "Hello! Welcome to our WhatsApp service. What's your name?",
|
275
|
-
transform: ->(input) { input.strip.titleize }
|
276
|
-
end
|
301
|
+
**Setup Simulator Controller:**
|
277
302
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
"support" => "๐ Contact Support",
|
283
|
-
"feedback" => "๐ฌ Give Feedback"
|
284
|
-
}
|
285
|
-
end
|
303
|
+
```ruby
|
304
|
+
# app/controllers/simulator_controller.rb
|
305
|
+
class SimulatorController < ApplicationController
|
306
|
+
include FlowChat::Simulator::Controller
|
286
307
|
|
287
|
-
|
288
|
-
|
289
|
-
show_information_menu
|
290
|
-
when "support"
|
291
|
-
contact_support
|
292
|
-
when "feedback"
|
293
|
-
collect_feedback
|
294
|
-
end
|
308
|
+
def index
|
309
|
+
flowchat_simulator
|
295
310
|
end
|
296
311
|
|
297
|
-
|
312
|
+
protected
|
298
313
|
|
299
|
-
def
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
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"
|
305
339
|
}
|
306
|
-
|
340
|
+
}
|
341
|
+
end
|
307
342
|
|
308
|
-
|
309
|
-
|
310
|
-
app.say "We're open Monday-Friday 9AM-6PM, Saturday 10AM-4PM. Closed Sundays."
|
311
|
-
when "location"
|
312
|
-
app.say "๐ Visit us at 123 Main Street, Downtown. We're next to the coffee shop!"
|
313
|
-
when "services"
|
314
|
-
app.say "๐ผ We offer: Web Development, Mobile Apps, Cloud Services, and IT Consulting."
|
315
|
-
end
|
343
|
+
def default_config_key
|
344
|
+
:local_whatsapp
|
316
345
|
end
|
317
346
|
|
318
|
-
def
|
319
|
-
|
320
|
-
|
321
|
-
"call" => "๐ Call Us",
|
322
|
-
"email" => "๐ง Email Us",
|
323
|
-
"chat" => "๐ฌ Continue Here"
|
324
|
-
}
|
325
|
-
end
|
347
|
+
def default_phone_number
|
348
|
+
"+1234567890"
|
349
|
+
end
|
326
350
|
|
327
|
-
|
328
|
-
|
329
|
-
app.say "๐ Call us at: +1-555-HELP (4357)\nAvailable Mon-Fri 9AM-5PM"
|
330
|
-
when "email"
|
331
|
-
app.say "๐ง Email us at: support@company.com\nWe typically respond within 24 hours"
|
332
|
-
when "chat"
|
333
|
-
app.say "๐ฌ Great! Please describe your issue and we'll help you right away."
|
334
|
-
end
|
351
|
+
def default_contact_name
|
352
|
+
"John Doe"
|
335
353
|
end
|
354
|
+
end
|
355
|
+
```
|
336
356
|
|
337
|
-
|
338
|
-
rating = app.screen(:rating) do |prompt|
|
339
|
-
prompt.select "How would you rate our service?", {
|
340
|
-
"5" => "โญโญโญโญโญ Excellent",
|
341
|
-
"4" => "โญโญโญโญ Good",
|
342
|
-
"3" => "โญโญโญ Average",
|
343
|
-
"2" => "โญโญ Poor",
|
344
|
-
"1" => "โญ Very Poor"
|
345
|
-
}
|
346
|
-
end
|
357
|
+
**Add Simulator Route:**
|
347
358
|
|
348
|
-
|
349
|
-
|
350
|
-
|
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
|
351
382
|
|
352
|
-
|
353
|
-
|
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
|
354
388
|
|
355
|
-
|
389
|
+
processor.run WelcomeFlow, :main_page
|
356
390
|
end
|
357
391
|
end
|
358
392
|
```
|
359
393
|
|
360
|
-
|
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.
|
361
406
|
|
362
407
|
## ๐ง Reusable WhatsApp Client
|
363
408
|
|
@@ -437,7 +482,6 @@ class NotificationService
|
|
437
482
|
end
|
438
483
|
```
|
439
484
|
|
440
|
-
๐ก **See [examples/whatsapp_controller_modes.rb](examples/whatsapp_controller_modes.rb) for comprehensive usage examples.**
|
441
485
|
|
442
486
|
## Cross-Platform Compatibility
|
443
487
|
|
@@ -905,6 +949,54 @@ config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
905
949
|
|
906
950
|
## Testing
|
907
951
|
|
952
|
+
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.
|
953
|
+
|
954
|
+
### Testing Approaches Overview
|
955
|
+
|
956
|
+
FlowChat supports two main testing strategies:
|
957
|
+
|
958
|
+
**๐ฏ Option 1: Simulator Mode (Recommended)**
|
959
|
+
- Bypasses WhatsApp API entirely
|
960
|
+
- No webhook signature validation required
|
961
|
+
- Responses returned as JSON instead of sent to WhatsApp
|
962
|
+
- Perfect for unit and integration testing
|
963
|
+
- Works with built-in web simulator interface
|
964
|
+
|
965
|
+
**๐ Option 2: Skip Signature Validation**
|
966
|
+
- Tests real webhook endpoints without security complexity
|
967
|
+
- Useful for staging environments
|
968
|
+
- Set `skip_signature_validation = true`
|
969
|
+
|
970
|
+
### Environment-Specific Testing Configuration
|
971
|
+
|
972
|
+
Configure testing behavior per environment:
|
973
|
+
|
974
|
+
```ruby
|
975
|
+
# config/initializers/flowchat.rb
|
976
|
+
case Rails.env
|
977
|
+
when 'development'
|
978
|
+
# Enable simulator mode for testing
|
979
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
980
|
+
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_dev"
|
981
|
+
|
982
|
+
when 'test'
|
983
|
+
# Enable simulator mode for automated testing
|
984
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
985
|
+
FlowChat::Config.simulator_secret = "test_secret"
|
986
|
+
|
987
|
+
when 'staging'
|
988
|
+
# Use inline mode but allow simulator for testing
|
989
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline
|
990
|
+
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
991
|
+
|
992
|
+
when 'production'
|
993
|
+
# Background processing in production, no simulator
|
994
|
+
FlowChat::Config.whatsapp.message_handling_mode = :background
|
995
|
+
FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
|
996
|
+
FlowChat::Config.simulator_secret = nil
|
997
|
+
end
|
998
|
+
```
|
999
|
+
|
908
1000
|
### Unit Testing Flows
|
909
1001
|
|
910
1002
|
Test your flows in isolation using the provided test helpers:
|
@@ -929,257 +1021,288 @@ class WelcomeFlowTest < Minitest::Test
|
|
929
1021
|
|
930
1022
|
assert_equal "Hello, John Doe! Welcome to FlowChat.", error.prompt
|
931
1023
|
end
|
932
|
-
|
933
|
-
def test_welcome_flow_without_input
|
934
|
-
@context.input = nil
|
935
|
-
app = FlowChat::Ussd::App.new(@context)
|
936
|
-
|
937
|
-
error = assert_raises(FlowChat::Interrupt::Prompt) do
|
938
|
-
flow = WelcomeFlow.new(app)
|
939
|
-
flow.main_page
|
940
|
-
end
|
941
|
-
|
942
|
-
assert_equal "Welcome! What's your name?", error.prompt
|
943
|
-
end
|
944
1024
|
end
|
945
1025
|
```
|
946
1026
|
|
947
1027
|
### Integration Testing
|
948
1028
|
|
949
|
-
|
1029
|
+
#### Simulator Mode Testing
|
950
1030
|
|
951
1031
|
```ruby
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
1032
|
+
test "complete flow via simulator" do
|
1033
|
+
# Generate valid simulator cookie for authentication
|
1034
|
+
valid_cookie = generate_simulator_cookie
|
1035
|
+
|
1036
|
+
webhook_payload = {
|
1037
|
+
entry: [{ changes: [{ value: { messages: [{ from: "1234567890", text: { body: "Hello" }, type: "text" }] } }] }],
|
1038
|
+
simulator_mode: true
|
1039
|
+
}
|
959
1040
|
|
960
|
-
|
961
|
-
|
962
|
-
# Second request - provide phone, ask for age
|
963
|
-
# Third request - provide age, complete registration
|
964
|
-
end
|
1041
|
+
post "/whatsapp/webhook", params: webhook_payload, cookies: { flowchat_simulator: valid_cookie }
|
1042
|
+
assert_response :success
|
965
1043
|
end
|
966
1044
|
```
|
967
1045
|
|
968
|
-
|
969
|
-
|
970
|
-
Test your custom middleware in isolation:
|
1046
|
+
#### Testing with Skipped Validation
|
971
1047
|
|
972
1048
|
```ruby
|
973
|
-
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
context.input = "test input"
|
981
|
-
|
982
|
-
# Capture log output
|
983
|
-
log_output = StringIO.new
|
984
|
-
Rails.stub(:logger, Logger.new(log_output)) do
|
985
|
-
type, prompt, choices = middleware.call(context)
|
986
|
-
|
987
|
-
assert_equal :prompt, type
|
988
|
-
assert_equal "Test response", prompt
|
989
|
-
assert_includes log_output.string, "Processing USSD request: test input"
|
990
|
-
assert_includes log_output.string, "Response: Test response"
|
991
|
-
end
|
992
|
-
end
|
1049
|
+
test "webhook processing with skipped validation" do
|
1050
|
+
config = FlowChat::Whatsapp::Configuration.new
|
1051
|
+
config.skip_signature_validation = true # Skip for testing
|
1052
|
+
|
1053
|
+
# No signature required when validation is skipped
|
1054
|
+
post "/whatsapp/webhook", params: webhook_payload.to_json
|
1055
|
+
assert_response :success
|
993
1056
|
end
|
994
1057
|
```
|
995
1058
|
|
996
|
-
###
|
997
|
-
|
998
|
-
Test runtime middleware modifications:
|
1059
|
+
### Test Helper Methods
|
999
1060
|
|
1000
1061
|
```ruby
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
define_method(:call) do |context|
|
1013
|
-
custom_middleware_called = true
|
1014
|
-
@app.call(context)
|
1015
|
-
end
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
processor.run(TestFlow, :main_page) do |stack|
|
1019
|
-
stack.use custom_middleware
|
1020
|
-
stack.insert_before FlowChat::Ussd::Middleware::Executor, custom_middleware
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
assert custom_middleware_called, "Custom middleware should have been executed"
|
1024
|
-
end
|
1062
|
+
private
|
1063
|
+
|
1064
|
+
def generate_webhook_signature(payload_json, secret = @app_secret)
|
1065
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, payload_json)
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
def generate_simulator_cookie(secret = FlowChat::Config.simulator_secret)
|
1069
|
+
timestamp = Time.now.to_i
|
1070
|
+
message = "simulator:#{timestamp}"
|
1071
|
+
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, message)
|
1072
|
+
"#{timestamp}:#{signature}"
|
1025
1073
|
end
|
1026
1074
|
```
|
1027
1075
|
|
1028
|
-
|
1076
|
+
## Configuration Reference
|
1029
1077
|
|
1030
|
-
|
1078
|
+
### Framework Configuration
|
1031
1079
|
|
1032
1080
|
```ruby
|
1033
|
-
|
1034
|
-
include FlowChat::Simulator::Controller
|
1035
|
-
|
1036
|
-
def index
|
1037
|
-
flowchat_simulator
|
1038
|
-
end
|
1039
|
-
|
1040
|
-
protected
|
1041
|
-
|
1042
|
-
def configurations
|
1043
|
-
{
|
1044
|
-
production_ussd: {
|
1045
|
-
name: "Production USSD",
|
1046
|
-
icon: "๐ญ",
|
1047
|
-
processor_type: "ussd",
|
1048
|
-
provider: "nalo",
|
1049
|
-
endpoint: "/ussd",
|
1050
|
-
color: "#28a745"
|
1051
|
-
},
|
1052
|
-
staging_whatsapp: {
|
1053
|
-
name: "Staging WhatsApp",
|
1054
|
-
icon: "๐งช",
|
1055
|
-
processor_type: "whatsapp",
|
1056
|
-
provider: "cloud_api",
|
1057
|
-
endpoint: "/whatsapp/webhook",
|
1058
|
-
color: "#17a2b8"
|
1059
|
-
},
|
1060
|
-
local_ussd: {
|
1061
|
-
name: "Local USSD",
|
1062
|
-
icon: "๐ป",
|
1063
|
-
processor_type: "ussd",
|
1064
|
-
provider: "nalo",
|
1065
|
-
endpoint: "http://localhost:3000/ussd",
|
1066
|
-
color: "#6f42c1"
|
1067
|
-
}
|
1068
|
-
}
|
1069
|
-
end
|
1081
|
+
# config/initializers/flowchat.rb
|
1070
1082
|
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1083
|
+
# Core configuration
|
1084
|
+
FlowChat::Config.logger = Rails.logger
|
1085
|
+
FlowChat::Config.cache = Rails.cache
|
1086
|
+
FlowChat::Config.simulator_secret = "your_secure_secret_here"
|
1087
|
+
|
1088
|
+
# USSD configuration
|
1089
|
+
FlowChat::Config.ussd.pagination_page_size = 140
|
1090
|
+
FlowChat::Config.ussd.pagination_next_option = "#"
|
1091
|
+
FlowChat::Config.ussd.pagination_next_text = "More"
|
1092
|
+
FlowChat::Config.ussd.pagination_back_option = "0"
|
1093
|
+
FlowChat::Config.ussd.pagination_back_text = "Back"
|
1094
|
+
FlowChat::Config.ussd.resumable_sessions_enabled = true
|
1095
|
+
FlowChat::Config.ussd.resumable_sessions_timeout_seconds = 300
|
1096
|
+
|
1097
|
+
# WhatsApp configuration
|
1098
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline # :inline, :background, :simulator
|
1099
|
+
FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
|
1100
|
+
```
|
1074
1101
|
|
1075
|
-
|
1076
|
-
"+254712345678"
|
1077
|
-
end
|
1102
|
+
### WhatsApp Security Configuration
|
1078
1103
|
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1104
|
+
```ruby
|
1105
|
+
# Per-configuration security settings
|
1106
|
+
config = FlowChat::Whatsapp::Configuration.new
|
1107
|
+
config.app_secret = "your_whatsapp_app_secret" # Required for signature validation
|
1108
|
+
config.skip_signature_validation = false # Default: false (enforce validation)
|
1109
|
+
|
1110
|
+
# Security modes:
|
1111
|
+
# 1. Full security (production)
|
1112
|
+
config.app_secret = "secret"
|
1113
|
+
config.skip_signature_validation = false
|
1114
|
+
|
1115
|
+
# 2. Development mode (disable validation)
|
1116
|
+
config.app_secret = nil
|
1117
|
+
config.skip_signature_validation = true
|
1083
1118
|
```
|
1084
1119
|
|
1085
|
-
|
1086
|
-
|
1087
|
-
**Key Features:**
|
1088
|
-
- ๐ **Platform Toggle** - Switch between USSD and WhatsApp modes with configuration selection
|
1089
|
-
- ๐ฑ **USSD Mode** - Classic green-screen terminal simulation with provider support (Nalo, Nsano)
|
1090
|
-
- ๐ฌ **WhatsApp Mode** - Full WhatsApp interface with interactive buttons, lists, and rich messaging
|
1091
|
-
- โ๏ธ **Multi-Environment** - Support for different configurations (local, staging, production)
|
1092
|
-
- ๐จ **Modern UI** - Beautiful, responsive interface with real-time status indicators
|
1093
|
-
- ๐ **Request Logging** - View all HTTP requests and responses in real-time
|
1094
|
-
- ๐ง **Developer Tools** - Character counts, connection status, and comprehensive error handling
|
1120
|
+
## Best Practices
|
1095
1121
|
|
1096
|
-
|
1097
|
-
- **USSD**: Shows traditional terminal-style interface with character limits and pagination
|
1098
|
-
- **WhatsApp**: Displays realistic WhatsApp chat interface with support for interactive elements
|
1122
|
+
### 1. Security Best Practices
|
1099
1123
|
|
1100
|
-
|
1124
|
+
**Production Security Checklist:**
|
1101
1125
|
|
1102
|
-
|
1126
|
+
โ
**Always configure app_secret** for webhook validation
|
1127
|
+
```ruby
|
1128
|
+
config.app_secret = "your_whatsapp_app_secret" # Never leave empty in production
|
1129
|
+
config.skip_signature_validation = false # Never disable in production
|
1130
|
+
```
|
1103
1131
|
|
1104
|
-
|
1132
|
+
โ
**Use secure simulator secrets**
|
1133
|
+
```ruby
|
1134
|
+
# Use Rails secret_key_base + suffix for uniqueness
|
1135
|
+
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_simulator"
|
1105
1136
|
|
1106
|
-
|
1137
|
+
# Or use environment variables
|
1138
|
+
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
1139
|
+
```
|
1107
1140
|
|
1141
|
+
โ
**Environment-specific configuration**
|
1108
1142
|
```ruby
|
1109
|
-
#
|
1110
|
-
|
1111
|
-
#
|
1112
|
-
|
1113
|
-
|
1114
|
-
class RegistrationFlow < FlowChat::Flow
|
1115
|
-
# Handle user registration
|
1143
|
+
# Different security levels per environment
|
1144
|
+
if Rails.env.production?
|
1145
|
+
config.skip_signature_validation = false # Enforce validation
|
1146
|
+
else
|
1147
|
+
config.skip_signature_validation = true # Allow for development
|
1116
1148
|
end
|
1149
|
+
```
|
1117
1150
|
|
1118
|
-
|
1119
|
-
|
1120
|
-
|
1151
|
+
โ
**Enable simulator only when needed**
|
1152
|
+
```ruby
|
1153
|
+
# Only enable simulator in development/staging (default)
|
1154
|
+
enable_simulator = Rails.env.development? || Rails.env.staging?
|
1155
|
+
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: enable_simulator)
|
1121
1156
|
```
|
1122
1157
|
|
1123
|
-
###
|
1158
|
+
### 5. Choose the Right WhatsApp Mode
|
1124
1159
|
|
1125
|
-
|
1160
|
+
Configure the appropriate mode based on your environment and requirements:
|
1126
1161
|
|
1127
1162
|
```ruby
|
1128
|
-
#
|
1129
|
-
app.screen(:customer_phone_number) { |p| p.ask "Phone:" }
|
1130
|
-
app.screen(:payment_confirmation) { |p| p.yes? "Confirm payment?" }
|
1131
|
-
|
1132
|
-
# Avoid
|
1133
|
-
app.screen(:input1) { |p| p.ask "Phone:" }
|
1134
|
-
app.screen(:confirm) { |p| p.yes? "Confirm payment?" }
|
1135
|
-
```
|
1163
|
+
# config/initializers/flowchat.rb
|
1136
1164
|
|
1137
|
-
|
1165
|
+
# Development/Testing - use simulator mode with security
|
1166
|
+
if Rails.env.development? || Rails.env.test?
|
1167
|
+
FlowChat::Config.whatsapp.message_handling_mode = :simulator
|
1168
|
+
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_dev"
|
1169
|
+
end
|
1138
1170
|
|
1139
|
-
|
1171
|
+
# Staging - use inline mode with full security
|
1172
|
+
if Rails.env.staging?
|
1173
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline
|
1174
|
+
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
1175
|
+
end
|
1140
1176
|
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1146
|
-
return "Amount must be positive" if amount <= 0
|
1147
|
-
return "Maximum amount is $1000" if amount > 1000
|
1148
|
-
nil
|
1149
|
-
}
|
1177
|
+
# Production - use background mode with full security
|
1178
|
+
if Rails.env.production?
|
1179
|
+
FlowChat::Config.whatsapp.message_handling_mode = :background
|
1180
|
+
FlowChat::Config.whatsapp.background_job_class = 'WhatsappMessageJob'
|
1181
|
+
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
1150
1182
|
end
|
1151
1183
|
```
|
1152
1184
|
|
1153
|
-
###
|
1185
|
+
### 6. Error Handling Best Practices
|
1154
1186
|
|
1155
|
-
|
1187
|
+
Handle security and configuration errors gracefully:
|
1156
1188
|
|
1157
1189
|
```ruby
|
1158
|
-
def
|
1190
|
+
def webhook
|
1159
1191
|
begin
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1192
|
+
processor = FlowChat::Whatsapp::Processor.new(self, enable_simulator: Rails.env.development?) do |config|
|
1193
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
1194
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
processor.run WelcomeFlow, :main_page
|
1198
|
+
|
1199
|
+
rescue FlowChat::Whatsapp::ConfigurationError => e
|
1200
|
+
Rails.logger.error "WhatsApp configuration error: #{e.message}"
|
1201
|
+
head :internal_server_error
|
1202
|
+
|
1203
|
+
rescue => e
|
1204
|
+
Rails.logger.error "Unexpected error processing WhatsApp webhook: #{e.message}"
|
1205
|
+
head :internal_server_error
|
1165
1206
|
end
|
1166
1207
|
end
|
1167
1208
|
```
|
1168
1209
|
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1210
|
+
## Appendix
|
1211
|
+
|
1212
|
+
### Context Variables Reference
|
1213
|
+
|
1214
|
+
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.
|
1215
|
+
|
1216
|
+
#### Core Context Variables
|
1217
|
+
|
1218
|
+
**Request Variables**
|
1219
|
+
|
1220
|
+
| Variable | Description |
|
1221
|
+
|----------|-------------|
|
1222
|
+
| `context.input` | Current user input |
|
1223
|
+
| `context["request.id"]` | Unique request ID |
|
1224
|
+
| `context["request.timestamp"]` | Request timestamp |
|
1225
|
+
| `context["request.msisdn"]` | User's phone number |
|
1226
|
+
| `context["request.gateway"]` | Current gateway (`:whatsapp_cloud_api`, `:ussd_nalo`) |
|
1227
|
+
| `context["enable_simulator"]` | Whether simulator mode is enabled for this request |
|
1228
|
+
| `context["simulator_mode"]` | Whether simulator mode is active |
|
1229
|
+
|
1230
|
+
**Flow Variables**
|
1231
|
+
|
1232
|
+
| Variable | Description |
|
1233
|
+
|----------|-------------|
|
1234
|
+
| `context["flow.name"]` | Current flow name |
|
1235
|
+
| `context["flow.class"]` | Current flow class |
|
1236
|
+
| `context["flow.action"]` | Current flow action/method |
|
1237
|
+
|
1238
|
+
**Session Variables**
|
1239
|
+
|
1240
|
+
| Variable | Description |
|
1241
|
+
|----------|-------------|
|
1242
|
+
| `context["session.id"]` | Current session ID |
|
1243
|
+
| `context["session.store"]` | Session data store |
|
1244
|
+
|
1245
|
+
**Controller Variables**
|
1246
|
+
|
1247
|
+
| Variable | Description |
|
1248
|
+
|----------|-------------|
|
1249
|
+
| `context.controller` | Current controller instance |
|
1250
|
+
|
1251
|
+
#### WhatsApp-Specific Variables
|
1252
|
+
|
1253
|
+
| Variable | Description |
|
1254
|
+
|----------|-------------|
|
1255
|
+
| `context["whatsapp.message_result"]` | Result of last message send |
|
1256
|
+
| `context["whatsapp.message_id"]` | WhatsApp message ID |
|
1257
|
+
| `context["whatsapp.contact_name"]` | WhatsApp contact name |
|
1258
|
+
| `context["whatsapp.location"]` | Location data if shared |
|
1259
|
+
| `context["whatsapp.media"]` | Media data if attached |
|
1260
|
+
|
1261
|
+
#### USSD-Specific Variables
|
1262
|
+
|
1263
|
+
| Variable | Description |
|
1264
|
+
|----------|-------------|
|
1265
|
+
| `context["ussd.request"]` | Original USSD request |
|
1266
|
+
| `context["ussd.response"]` | USSD response object |
|
1267
|
+
| `context["ussd.pagination"]` | Pagination data for long messages |
|
1268
|
+
|
1269
|
+
#### Session Data Variables
|
1270
|
+
|
1271
|
+
| Variable | Description |
|
1272
|
+
|----------|-------------|
|
1273
|
+
| `context["$started_at$"]` | When the conversation started |
|
1274
|
+
|
1275
|
+
#### Usage Notes
|
1276
|
+
|
1277
|
+
1. **Access Methods:**
|
1278
|
+
```ruby
|
1279
|
+
# Direct access
|
1280
|
+
context.input
|
1281
|
+
context["request.msisdn"]
|
1282
|
+
|
1283
|
+
# Through app object in flows
|
1284
|
+
app.phone_number
|
1285
|
+
app.contact_name
|
1286
|
+
app.message_id
|
1287
|
+
```
|
1288
|
+
|
1289
|
+
2. **Gateway-Specific Variables:**
|
1290
|
+
- WhatsApp variables are only available when using WhatsApp gateway
|
1291
|
+
- USSD variables are only available when using USSD gateway
|
1292
|
+
- Core variables are available across all gateways
|
1293
|
+
|
1294
|
+
3. **Session Persistence:**
|
1295
|
+
- Session data persists across requests
|
1296
|
+
- WhatsApp sessions expire after 7 days
|
1297
|
+
- USSD sessions expire after 1 hour
|
1298
|
+
- Default session TTL is 1 day
|
1299
|
+
|
1300
|
+
4. **Security:**
|
1301
|
+
- Webhook signatures are validated for WhatsApp requests
|
1302
|
+
- Simulator mode requires valid simulator cookie
|
1303
|
+
- Session data is encrypted at rest
|
1304
|
+
|
1305
|
+
5. **Flow Control:**
|
1306
|
+
- Context variables can be used to control flow logic
|
1307
|
+
- Session data can be used to maintain state
|
1308
|
+
- Request data can be used for validation
|