flow_chat 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +11 -11
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/ussd_controller.rb +9 -9
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +10 -30
- data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +81 -33
- data/lib/flow_chat/http/renderer.rb +3 -3
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +23 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/middleware.rb +25 -9
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +84 -25
- data/docs/flows.md +0 -320
- data/docs/http-gateway-protocol.md +0 -432
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -146
- data/lib/flow_chat/http/app.rb +0 -6
- data/lib/flow_chat/http/middleware/executor.rb +0 -24
- data/lib/flow_chat/http/processor.rb +0 -33
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
data/docs/configuration.md
CHANGED
|
@@ -1,360 +1,690 @@
|
|
|
1
|
-
# Configuration
|
|
1
|
+
# FlowChat Configuration
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This guide covers all configuration options for FlowChat, from basic setup to advanced customization for production deployments.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Global Configuration
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
# config/initializers/flowchat.rb
|
|
7
|
+
Configure FlowChat globally in `config/initializers/flow_chat.rb`:
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
```ruby
|
|
10
|
+
# Basic configuration
|
|
11
11
|
FlowChat::Config.logger = Rails.logger
|
|
12
12
|
FlowChat::Config.cache = Rails.cache
|
|
13
|
-
FlowChat::Config.simulator_secret = "your_secure_secret_here"
|
|
14
13
|
|
|
15
|
-
# Validation
|
|
16
|
-
FlowChat::Config.combine_validation_error_with_message = true
|
|
14
|
+
# Validation behavior
|
|
15
|
+
FlowChat::Config.combine_validation_error_with_message = true
|
|
17
16
|
|
|
18
|
-
#
|
|
19
|
-
FlowChat.
|
|
17
|
+
# Simulator settings (for development/testing)
|
|
18
|
+
FlowChat::Config.simulator_secret = "your_simulator_secret"
|
|
20
19
|
```
|
|
21
20
|
|
|
22
|
-
##
|
|
21
|
+
## Platform-Specific Configuration
|
|
22
|
+
|
|
23
|
+
### USSD Configuration
|
|
23
24
|
|
|
24
25
|
```ruby
|
|
25
|
-
#
|
|
26
|
-
FlowChat::Config.
|
|
27
|
-
FlowChat::Config.
|
|
28
|
-
FlowChat::Config.
|
|
29
|
-
|
|
30
|
-
#
|
|
31
|
-
# :flow - separate sessions per flow class
|
|
32
|
-
# :platform - separate sessions per platform (ussd, whatsapp)
|
|
33
|
-
# :gateway - separate sessions per gateway
|
|
34
|
-
# [] - global sessions (no boundaries)
|
|
35
|
-
|
|
36
|
-
# Available identifier options:
|
|
37
|
-
# nil - platform chooses default (:request_id for USSD, :msisdn for WhatsApp)
|
|
38
|
-
# :msisdn - use phone number (durable sessions)
|
|
39
|
-
# :request_id - use request ID (ephemeral sessions)
|
|
26
|
+
# USSD pagination settings
|
|
27
|
+
FlowChat::Config.ussd.pagination_page_size = 160 # Characters per page
|
|
28
|
+
FlowChat::Config.ussd.pagination_next_option = "#" # Next page option
|
|
29
|
+
FlowChat::Config.ussd.pagination_back_option = "0" # Previous page option
|
|
30
|
+
FlowChat::Config.ussd.pagination_next_text = "More" # Next page text
|
|
31
|
+
FlowChat::Config.ussd.pagination_back_text = "Back" # Previous page text
|
|
40
32
|
```
|
|
41
33
|
|
|
42
|
-
|
|
34
|
+
### WhatsApp Configuration
|
|
43
35
|
|
|
44
36
|
```ruby
|
|
45
|
-
#
|
|
46
|
-
FlowChat::Config.
|
|
47
|
-
FlowChat::Config.ussd.pagination_next_option = "#" # option to go to next page
|
|
48
|
-
FlowChat::Config.ussd.pagination_next_text = "More" # text for next option
|
|
49
|
-
FlowChat::Config.ussd.pagination_back_option = "0" # option to go back
|
|
50
|
-
FlowChat::Config.ussd.pagination_back_text = "Back" # text for back option
|
|
37
|
+
# WhatsApp API configuration
|
|
38
|
+
FlowChat::Config.whatsapp.api_base_url = "https://graph.facebook.com/v23.0"
|
|
51
39
|
```
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
### HTTP Configuration
|
|
54
42
|
|
|
55
43
|
```ruby
|
|
56
|
-
#
|
|
57
|
-
FlowChat::Config.
|
|
58
|
-
FlowChat::Config.
|
|
44
|
+
# HTTP gateway settings
|
|
45
|
+
FlowChat::Config.http.default_gateway = :simple
|
|
46
|
+
FlowChat::Config.http.request_timeout = 30
|
|
47
|
+
FlowChat::Config.http.response_format = :json
|
|
59
48
|
```
|
|
60
49
|
|
|
61
|
-
|
|
50
|
+
## Processor Configuration
|
|
62
51
|
|
|
63
|
-
|
|
52
|
+
Configure processors for each request:
|
|
64
53
|
|
|
65
|
-
```
|
|
66
|
-
|
|
54
|
+
```ruby
|
|
55
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
56
|
+
# Gateway configuration
|
|
57
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
58
|
+
|
|
59
|
+
# Session configuration
|
|
60
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
61
|
+
config.use_session_config(
|
|
62
|
+
boundaries: [:flow, :platform, :gateway],
|
|
63
|
+
identifier: :msisdn,
|
|
64
|
+
hash_identifiers: true
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Middleware configuration
|
|
68
|
+
config.use_middleware LoggingMiddleware
|
|
69
|
+
config.use_middleware AuthenticationMiddleware
|
|
70
|
+
|
|
71
|
+
# Convenience methods
|
|
72
|
+
config.use_durable_sessions
|
|
73
|
+
config.use_cross_platform_sessions
|
|
74
|
+
config.use_url_isolation
|
|
75
|
+
end
|
|
67
76
|
```
|
|
68
77
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
## Session Configuration
|
|
79
|
+
|
|
80
|
+
### Session Boundaries
|
|
81
|
+
|
|
82
|
+
Control how session IDs are generated:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# Available boundaries
|
|
86
|
+
boundaries: [
|
|
87
|
+
:flow, # Separate sessions per flow class
|
|
88
|
+
:platform, # Separate sessions per platform (ussd, whatsapp, http)
|
|
89
|
+
:gateway, # Separate sessions per gateway (nalo, cloud_api, etc.)
|
|
90
|
+
:url # Separate sessions per URL (multi-tenancy)
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
# Examples
|
|
94
|
+
config.use_session_config(boundaries: [:flow]) # "survey_flow:user123"
|
|
95
|
+
config.use_session_config(boundaries: [:flow, :platform]) # "survey_flow:ussd:user123"
|
|
96
|
+
config.use_session_config(boundaries: [:flow, :url]) # "survey_flow:tenant1.app.com:user123"
|
|
78
97
|
```
|
|
79
98
|
|
|
80
|
-
|
|
99
|
+
### Custom Session ID Generation
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
For complete control over session ID format, use a block:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Custom session ID with proc/block
|
|
105
|
+
config.use_session_config do |context|
|
|
106
|
+
# Access full context for custom logic
|
|
107
|
+
user_phone = context["request.msisdn"]
|
|
108
|
+
flow_name = context["flow.name"]
|
|
109
|
+
gateway = context["request.gateway"]
|
|
110
|
+
timestamp = Time.current.strftime("%Y%m%d")
|
|
111
|
+
|
|
112
|
+
# Return your custom session ID
|
|
113
|
+
"#{flow_name}_#{gateway}_#{timestamp}_#{hash_phone(user_phone)}"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Multi-tenant example
|
|
117
|
+
config.use_session_config do |context|
|
|
118
|
+
tenant_id = extract_tenant(context)
|
|
119
|
+
user_id = context["request.user_id"] || context["request.msisdn"]
|
|
120
|
+
"tenant_#{tenant_id}_user_#{user_id.hash.abs}"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# API-based session IDs
|
|
124
|
+
config.use_session_config do |context|
|
|
125
|
+
api_key = context.controller.request.headers["X-API-Key"]
|
|
126
|
+
"api_#{Digest::SHA256.hexdigest(api_key)[0,8]}_#{context['request.id']}"
|
|
127
|
+
end
|
|
90
128
|
```
|
|
91
129
|
|
|
92
|
-
|
|
130
|
+
**Note**: When using a custom proc, it takes precedence over all boundary and identifier settings. The proc receives the full FlowChat context and should return a string session ID.
|
|
131
|
+
|
|
132
|
+
### Session Identifiers
|
|
133
|
+
|
|
134
|
+
Choose what identifies a user session:
|
|
93
135
|
|
|
94
136
|
```ruby
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
config.
|
|
102
|
-
config.
|
|
103
|
-
#
|
|
137
|
+
# Identifier types
|
|
138
|
+
:request_id # Ephemeral (new session each request)
|
|
139
|
+
:user_id # Durable using user_id field
|
|
140
|
+
:msisdn # Durable using phone number
|
|
141
|
+
|
|
142
|
+
# Examples
|
|
143
|
+
config.use_session_config(identifier: :request_id) # Default for HTTP
|
|
144
|
+
config.use_session_config(identifier: :msisdn) # Default for WhatsApp/USSD
|
|
145
|
+
config.use_session_config(identifier: :user_id) # For authenticated users
|
|
104
146
|
```
|
|
105
147
|
|
|
106
|
-
|
|
148
|
+
### Session Stores
|
|
149
|
+
|
|
150
|
+
Choose where session data is stored:
|
|
107
151
|
|
|
108
152
|
```ruby
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
staging_config = FlowChat::Whatsapp::Configuration.new(:staging)
|
|
116
|
-
staging_config.access_token = ENV['STAGING_WHATSAPP_TOKEN']
|
|
117
|
-
# ... other settings
|
|
153
|
+
# Built-in session stores
|
|
154
|
+
config.use_session_store FlowChat::Session::CacheSessionStore # Rails sessions
|
|
155
|
+
config.use_session_store FlowChat::Session::CacheSessionStore # Rails cache
|
|
156
|
+
|
|
157
|
+
# Custom session store
|
|
158
|
+
config.use_session_store MyCompany::CustomSessionStore
|
|
118
159
|
```
|
|
119
160
|
|
|
120
|
-
|
|
161
|
+
### Convenience Methods
|
|
121
162
|
|
|
122
163
|
```ruby
|
|
123
|
-
#
|
|
124
|
-
config
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
164
|
+
# Durable sessions (use phone number, survive timeouts)
|
|
165
|
+
config.use_durable_sessions
|
|
166
|
+
|
|
167
|
+
# Cross-platform sessions (same user across USSD/WhatsApp)
|
|
168
|
+
config.use_cross_platform_sessions
|
|
169
|
+
|
|
170
|
+
# URL-based isolation (multi-tenancy)
|
|
171
|
+
config.use_url_isolation
|
|
128
172
|
```
|
|
129
173
|
|
|
130
|
-
##
|
|
174
|
+
## Gateway Configuration
|
|
175
|
+
|
|
176
|
+
### Built-in Gateways
|
|
177
|
+
|
|
178
|
+
#### USSD - Nalo Gateway
|
|
131
179
|
|
|
132
|
-
|
|
180
|
+
```ruby
|
|
181
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
182
|
+
|
|
183
|
+
# No additional configuration required for Nalo
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### WhatsApp - Cloud API Gateway
|
|
133
187
|
|
|
134
188
|
```ruby
|
|
135
|
-
#
|
|
136
|
-
config.
|
|
137
|
-
|
|
189
|
+
# Basic configuration (uses Rails credentials)
|
|
190
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
191
|
+
|
|
192
|
+
# Custom configuration
|
|
193
|
+
whatsapp_config = FlowChat::Whatsapp::Configuration.new
|
|
194
|
+
whatsapp_config.access_token = ENV["WHATSAPP_ACCESS_TOKEN"]
|
|
195
|
+
whatsapp_config.phone_number_id = ENV["WHATSAPP_PHONE_NUMBER_ID"]
|
|
196
|
+
whatsapp_config.verify_token = ENV["WHATSAPP_VERIFY_TOKEN"]
|
|
197
|
+
whatsapp_config.app_secret = ENV["WHATSAPP_APP_SECRET"]
|
|
198
|
+
whatsapp_config.skip_signature_validation = Rails.env.development?
|
|
199
|
+
|
|
200
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, whatsapp_config
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### HTTP - Simple Gateway
|
|
138
204
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
config.
|
|
205
|
+
```ruby
|
|
206
|
+
# HTTP gateway requires user_params hash
|
|
207
|
+
config.use_gateway FlowChat::Http::Gateway::Simple, {
|
|
208
|
+
session_id: "unique_session_id", # required
|
|
209
|
+
user_id: "user_identifier", # required
|
|
210
|
+
name: "John Doe", # optional
|
|
211
|
+
msisdn: "+256700123456", # optional
|
|
212
|
+
email: "user@example.com" # optional
|
|
213
|
+
}
|
|
142
214
|
```
|
|
143
215
|
|
|
144
|
-
|
|
216
|
+
**Required Parameters:**
|
|
217
|
+
- `session_id` - Unique identifier for the session
|
|
218
|
+
- `user_id` - User identifier
|
|
219
|
+
|
|
220
|
+
**Optional Parameters:**
|
|
221
|
+
- `name` - User's display name (sets `request.user_name`)
|
|
222
|
+
- `msisdn` - E.164 formatted phone number (sets `request.msisdn`)
|
|
223
|
+
- `email` - User email address (sets `request.email`)
|
|
224
|
+
|
|
225
|
+
**Note:** User input comes from `params["input"]` in the request. The `message_id` is auto-generated as a UUID.
|
|
226
|
+
|
|
227
|
+
### Custom Gateway Configuration
|
|
145
228
|
|
|
146
229
|
```ruby
|
|
147
|
-
#
|
|
148
|
-
|
|
230
|
+
# Custom gateway with configuration
|
|
231
|
+
my_config = MyCompany::CustomGatewayConfig.new
|
|
232
|
+
my_config.api_key = ENV["CUSTOM_API_KEY"]
|
|
233
|
+
my_config.endpoint = ENV["CUSTOM_ENDPOINT"]
|
|
149
234
|
|
|
150
|
-
|
|
151
|
-
FlowChat::Config.simulator_secret = ENV['FLOWCHAT_SIMULATOR_SECRET']
|
|
235
|
+
config.use_gateway MyCompany::CustomGateway, my_config
|
|
152
236
|
```
|
|
153
237
|
|
|
154
238
|
## Environment-Specific Configuration
|
|
155
239
|
|
|
240
|
+
### Development Configuration
|
|
241
|
+
|
|
156
242
|
```ruby
|
|
157
|
-
# config/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
FlowChat::Config.simulator_secret = Rails.application.secret_key_base + "_dev"
|
|
243
|
+
# config/environments/development.rb
|
|
244
|
+
Rails.application.configure do
|
|
245
|
+
# Enable detailed logging
|
|
246
|
+
config.log_level = :debug
|
|
162
247
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
248
|
+
# FlowChat development settings
|
|
249
|
+
config.after_initialize do
|
|
250
|
+
FlowChat::Config.logger = Rails.logger
|
|
251
|
+
FlowChat::Config.logger.level = Logger::DEBUG
|
|
252
|
+
|
|
253
|
+
# USSD settings for development
|
|
254
|
+
FlowChat::Config.ussd.pagination_page_size = 200 # Larger for easier testing
|
|
255
|
+
|
|
256
|
+
# WhatsApp settings for development
|
|
257
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline
|
|
258
|
+
|
|
259
|
+
# Enable simulator
|
|
260
|
+
FlowChat::Config.simulator_secret = "dev_secret_123"
|
|
261
|
+
end
|
|
175
262
|
end
|
|
176
263
|
```
|
|
177
264
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
### USSD Processor
|
|
265
|
+
### Production Configuration
|
|
181
266
|
|
|
182
267
|
```ruby
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
# Session storage (required)
|
|
188
|
-
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
189
|
-
|
|
190
|
-
# Optional middleware
|
|
191
|
-
config.use_middleware MyCustomMiddleware
|
|
192
|
-
|
|
193
|
-
# Configure session boundaries
|
|
194
|
-
config.use_session_config(
|
|
195
|
-
boundaries: [:flow, :platform], # which boundaries to enforce
|
|
196
|
-
hash_identifiers: true, # hash identifiers for privacy
|
|
197
|
-
identifier: :msisdn # use MSISDN for durable sessions (optional)
|
|
198
|
-
)
|
|
268
|
+
# config/environments/production.rb
|
|
269
|
+
Rails.application.configure do
|
|
270
|
+
# Production logging
|
|
271
|
+
config.log_level = :info
|
|
199
272
|
|
|
200
|
-
#
|
|
201
|
-
config.
|
|
273
|
+
# FlowChat production settings
|
|
274
|
+
config.after_initialize do
|
|
275
|
+
FlowChat::Config.logger = Rails.logger
|
|
276
|
+
|
|
277
|
+
# USSD production settings
|
|
278
|
+
FlowChat::Config.ussd.pagination_page_size = 140 # Conservative for compatibility
|
|
279
|
+
|
|
280
|
+
# WhatsApp uses inline by default
|
|
281
|
+
|
|
282
|
+
# Disable simulator in production
|
|
283
|
+
FlowChat::Config.simulator_secret = nil
|
|
284
|
+
end
|
|
202
285
|
end
|
|
203
286
|
```
|
|
204
287
|
|
|
205
|
-
###
|
|
288
|
+
### Staging Configuration
|
|
206
289
|
|
|
207
290
|
```ruby
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
291
|
+
# config/environments/staging.rb
|
|
292
|
+
Rails.application.configure do
|
|
293
|
+
# Staging-specific settings
|
|
294
|
+
config.after_initialize do
|
|
295
|
+
FlowChat::Config.logger = Rails.logger
|
|
296
|
+
FlowChat::Config.logger.level = Logger::DEBUG # More verbose for staging
|
|
297
|
+
|
|
298
|
+
# Use background processing but with shorter delays
|
|
299
|
+
FlowChat::Config.whatsapp.message_handling_mode = :background
|
|
300
|
+
|
|
301
|
+
# Enable simulator with staging secret
|
|
302
|
+
FlowChat::Config.simulator_secret = ENV["STAGING_SIMULATOR_SECRET"]
|
|
303
|
+
end
|
|
217
304
|
end
|
|
218
305
|
```
|
|
219
306
|
|
|
220
|
-
##
|
|
307
|
+
## Multi-Tenant Configuration
|
|
221
308
|
|
|
222
|
-
###
|
|
309
|
+
### URL-Based Tenancy
|
|
223
310
|
|
|
224
311
|
```ruby
|
|
225
|
-
|
|
226
|
-
|
|
312
|
+
class MultiTenantController < ApplicationController
|
|
313
|
+
def process_request
|
|
314
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
315
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi, tenant_whatsapp_config
|
|
316
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
317
|
+
|
|
318
|
+
# Enable URL-based session isolation
|
|
319
|
+
config.use_url_isolation
|
|
320
|
+
|
|
321
|
+
# Optional: Additional tenant isolation
|
|
322
|
+
config.use_session_config(
|
|
323
|
+
boundaries: [:flow, :platform, :url],
|
|
324
|
+
identifier: :user_id
|
|
325
|
+
)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
processor.run tenant_flow_class, :main_action
|
|
329
|
+
end
|
|
227
330
|
|
|
228
|
-
|
|
331
|
+
private
|
|
229
332
|
|
|
230
|
-
|
|
333
|
+
def tenant_whatsapp_config
|
|
334
|
+
tenant = extract_tenant_from_request
|
|
335
|
+
tenant.whatsapp_configuration
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def tenant_flow_class
|
|
339
|
+
tenant = extract_tenant_from_request
|
|
340
|
+
tenant.flow_class
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def extract_tenant_from_request
|
|
344
|
+
# Extract tenant from subdomain, domain, or path
|
|
345
|
+
subdomain = request.subdomain
|
|
346
|
+
Tenant.find_by(subdomain: subdomain)
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
```
|
|
231
350
|
|
|
232
|
-
###
|
|
351
|
+
### Database-Based Tenancy
|
|
233
352
|
|
|
234
353
|
```ruby
|
|
235
|
-
|
|
236
|
-
|
|
354
|
+
class TenantSpecificController < ApplicationController
|
|
355
|
+
before_action :set_tenant
|
|
356
|
+
|
|
357
|
+
def process_request
|
|
358
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
359
|
+
config.use_gateway gateway_for_tenant
|
|
360
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
361
|
+
|
|
362
|
+
# Custom session boundaries for tenant isolation
|
|
363
|
+
config.use_session_config(
|
|
364
|
+
boundaries: [:flow, :platform],
|
|
365
|
+
identifier: :user_id
|
|
366
|
+
)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
processor.run @tenant.flow_class.constantize, :main_action
|
|
370
|
+
end
|
|
237
371
|
|
|
238
|
-
|
|
239
|
-
FlowChat::Session::Middleware
|
|
372
|
+
private
|
|
240
373
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
374
|
+
def set_tenant
|
|
375
|
+
@tenant = Tenant.find(params[:tenant_id])
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def gateway_for_tenant
|
|
379
|
+
case @tenant.platform
|
|
380
|
+
when 'whatsapp'
|
|
381
|
+
FlowChat::Whatsapp::Gateway::CloudApi
|
|
382
|
+
when 'ussd'
|
|
383
|
+
FlowChat::Ussd::Gateway::Nalo
|
|
384
|
+
else
|
|
385
|
+
FlowChat::Http::Gateway::Simple
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
244
389
|
```
|
|
245
390
|
|
|
246
|
-
|
|
391
|
+
## Security Configuration
|
|
392
|
+
|
|
393
|
+
### WhatsApp Signature Validation
|
|
247
394
|
|
|
248
395
|
```ruby
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
396
|
+
# Always validate signatures in production
|
|
397
|
+
whatsapp_config = FlowChat::Whatsapp::Configuration.new
|
|
398
|
+
whatsapp_config.app_secret = ENV["WHATSAPP_APP_SECRET"]
|
|
399
|
+
whatsapp_config.skip_signature_validation = false # Never skip in production
|
|
400
|
+
|
|
401
|
+
# Only skip validation in development if needed
|
|
402
|
+
whatsapp_config.skip_signature_validation = Rails.env.development?
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Session Security
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
# Hash sensitive identifiers
|
|
409
|
+
config.use_session_config(
|
|
410
|
+
identifier: :msisdn,
|
|
411
|
+
hash_identifiers: true # Phone numbers are hashed for privacy
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
# Implement custom session store with encryption
|
|
415
|
+
class EncryptedSessionStore < FlowChat::Session::CacheSessionStore
|
|
416
|
+
def set(key, value)
|
|
417
|
+
encrypted_value = encrypt(value.to_json)
|
|
418
|
+
super(key, encrypted_value)
|
|
252
419
|
end
|
|
253
420
|
|
|
254
|
-
def
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
421
|
+
def get(key)
|
|
422
|
+
encrypted_value = super(key)
|
|
423
|
+
return nil unless encrypted_value
|
|
424
|
+
|
|
425
|
+
decrypted = decrypt(encrypted_value)
|
|
426
|
+
JSON.parse(decrypted)
|
|
427
|
+
rescue
|
|
428
|
+
nil # Return nil if decryption fails
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
private
|
|
432
|
+
|
|
433
|
+
def encrypt(data)
|
|
434
|
+
# Use Rails credentials or environment variable
|
|
435
|
+
secret = Rails.application.credentials.session_encryption_key
|
|
436
|
+
crypt = ActiveSupport::MessageEncryptor.new(secret)
|
|
437
|
+
crypt.encrypt_and_sign(data)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def decrypt(encrypted_data)
|
|
441
|
+
secret = Rails.application.credentials.session_encryption_key
|
|
442
|
+
crypt = ActiveSupport::MessageEncryptor.new(secret)
|
|
443
|
+
crypt.decrypt_and_verify(encrypted_data)
|
|
259
444
|
end
|
|
260
445
|
end
|
|
261
446
|
|
|
262
|
-
# Use
|
|
263
|
-
config.
|
|
447
|
+
# Use encrypted session store
|
|
448
|
+
config.use_session_store EncryptedSessionStore
|
|
264
449
|
```
|
|
265
450
|
|
|
266
|
-
##
|
|
451
|
+
## Performance Configuration
|
|
267
452
|
|
|
268
|
-
###
|
|
453
|
+
### Redis Cache Configuration
|
|
269
454
|
|
|
270
455
|
```ruby
|
|
271
|
-
#
|
|
272
|
-
|
|
273
|
-
|
|
456
|
+
# config/initializers/redis.rb
|
|
457
|
+
redis_config = {
|
|
458
|
+
url: ENV["REDIS_URL"],
|
|
459
|
+
pool_size: ENV.fetch("REDIS_POOL_SIZE", 5).to_i,
|
|
460
|
+
pool_timeout: ENV.fetch("REDIS_POOL_TIMEOUT", 5).to_i
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
# Use Redis for session storage
|
|
464
|
+
Rails.application.configure do
|
|
465
|
+
config.cache_store = :redis_cache_store, redis_config
|
|
466
|
+
end
|
|
274
467
|
|
|
275
|
-
#
|
|
276
|
-
FlowChat::Config.
|
|
277
|
-
# User sees: "Invalid email format"
|
|
468
|
+
# FlowChat will automatically use Rails.cache
|
|
469
|
+
FlowChat::Config.cache = Rails.cache
|
|
278
470
|
```
|
|
279
471
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
### Job Class Setup
|
|
472
|
+
### Background Job Configuration
|
|
283
473
|
|
|
284
474
|
```ruby
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
475
|
+
# config/initializers/sidekiq.rb (if using Sidekiq)
|
|
476
|
+
Sidekiq.configure_server do |config|
|
|
477
|
+
config.redis = { url: ENV["REDIS_URL"] }
|
|
478
|
+
end
|
|
288
479
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
end
|
|
480
|
+
Sidekiq.configure_client do |config|
|
|
481
|
+
config.redis = { url: ENV["REDIS_URL"] }
|
|
292
482
|
end
|
|
483
|
+
|
|
484
|
+
# WhatsApp uses inline responses by defaul
|
|
293
485
|
```
|
|
294
486
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
487
|
+
### Database Connection Configuration
|
|
488
|
+
|
|
489
|
+
```ruby
|
|
490
|
+
# config/database.yml
|
|
491
|
+
production:
|
|
492
|
+
adapter: postgresql
|
|
493
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
|
|
494
|
+
timeout: 5000
|
|
495
|
+
# ... other database settings
|
|
496
|
+
|
|
497
|
+
# For high-volume USSD applications
|
|
498
|
+
checkout_timeout: 2
|
|
499
|
+
reaping_frequency: 10
|
|
500
|
+
```
|
|
298
501
|
|
|
299
|
-
|
|
502
|
+
## Monitoring Configuration
|
|
503
|
+
|
|
504
|
+
### Instrumentation Setup
|
|
300
505
|
|
|
301
506
|
```ruby
|
|
302
|
-
|
|
303
|
-
|
|
507
|
+
# config/initializers/flow_chat.rb
|
|
508
|
+
FlowChat.setup_instrumentation!
|
|
509
|
+
|
|
510
|
+
# Subscribe to FlowChat events
|
|
511
|
+
ActiveSupport::Notifications.subscribe("flow_chat.message_received") do |event|
|
|
512
|
+
# Log message received
|
|
513
|
+
Rails.logger.info "Message received: #{event.payload}"
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
ActiveSupport::Notifications.subscribe("flow_chat.message_sent") do |event|
|
|
517
|
+
# Log message sent
|
|
518
|
+
Rails.logger.info "Message sent: #{event.payload}"
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
ActiveSupport::Notifications.subscribe("flow_chat.flow_execution_error") do |event|
|
|
522
|
+
# Alert on flow errors
|
|
523
|
+
ErrorNotificationService.notify(event.payload)
|
|
524
|
+
end
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Custom Metrics
|
|
304
528
|
|
|
305
|
-
|
|
306
|
-
|
|
529
|
+
```ruby
|
|
530
|
+
# Custom metrics collector
|
|
531
|
+
class CustomMetricsCollector
|
|
532
|
+
def self.collect_metrics
|
|
533
|
+
{
|
|
534
|
+
active_sessions: count_active_sessions,
|
|
535
|
+
messages_per_minute: calculate_message_rate,
|
|
536
|
+
error_rate: calculate_error_rate
|
|
537
|
+
}
|
|
307
538
|
end
|
|
308
539
|
|
|
309
540
|
private
|
|
310
541
|
|
|
311
|
-
def
|
|
312
|
-
#
|
|
313
|
-
|
|
314
|
-
|
|
542
|
+
def self.count_active_sessions
|
|
543
|
+
# Implementation depends on your session store
|
|
544
|
+
Rails.cache.redis.keys("flow_chat:session:*").count
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def self.calculate_message_rate
|
|
548
|
+
# Implementation depends on your metrics storage
|
|
549
|
+
# Return messages processed in the last minute
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def self.calculate_error_rate
|
|
553
|
+
# Calculate error percentage
|
|
315
554
|
end
|
|
316
555
|
end
|
|
556
|
+
|
|
557
|
+
# Setup metrics collection
|
|
558
|
+
FlowChat::Config.metrics_collector = CustomMetricsCollector
|
|
317
559
|
```
|
|
318
560
|
|
|
319
|
-
|
|
561
|
+
## Testing Configuration
|
|
562
|
+
|
|
563
|
+
### Test Environment
|
|
320
564
|
|
|
321
565
|
```ruby
|
|
322
|
-
# config/
|
|
323
|
-
|
|
566
|
+
# config/environments/test.rb
|
|
567
|
+
Rails.application.configure do
|
|
568
|
+
config.after_initialize do
|
|
569
|
+
FlowChat::Config.logger = Rails.logger
|
|
570
|
+
FlowChat::Config.logger.level = Logger::ERROR # Reduce noise in tests
|
|
571
|
+
|
|
572
|
+
# Use memory cache for tests
|
|
573
|
+
FlowChat::Config.cache = ActiveSupport::Cache::MemoryStore.new
|
|
574
|
+
|
|
575
|
+
# Disable background processing in tests
|
|
576
|
+
FlowChat::Config.whatsapp.message_handling_mode = :inline
|
|
577
|
+
|
|
578
|
+
# Enable simulator for testing
|
|
579
|
+
FlowChat::Config.simulator_secret = "test_secret"
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Test Helper Configuration
|
|
324
585
|
|
|
325
|
-
|
|
326
|
-
|
|
586
|
+
```ruby
|
|
587
|
+
# test/test_helper.rb
|
|
588
|
+
class ActiveSupport::TestCase
|
|
589
|
+
def setup_flow_chat_test_environment
|
|
590
|
+
# Reset FlowChat state between tests
|
|
591
|
+
FlowChat::Config.cache.clear
|
|
592
|
+
|
|
593
|
+
# Mock external API calls
|
|
594
|
+
stub_whatsapp_api_calls
|
|
595
|
+
stub_ussd_gateway_calls
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
def create_test_processor(platform: :ussd, **options)
|
|
599
|
+
gateway_class = case platform
|
|
600
|
+
when :ussd then FlowChat::Ussd::Gateway::Nalo
|
|
601
|
+
when :whatsapp then FlowChat::Whatsapp::Gateway::CloudApi
|
|
602
|
+
when :http then FlowChat::Http::Gateway::Simple
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
FlowChat::Processor.new(MockController.new) do |config|
|
|
606
|
+
config.use_gateway gateway_class
|
|
607
|
+
config.use_session_store FlowChat::Session::MemorySessionStore
|
|
608
|
+
options.each { |key, value| config.send(key, value) }
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
end
|
|
327
612
|
```
|
|
328
613
|
|
|
329
|
-
##
|
|
614
|
+
## Troubleshooting Configuration
|
|
330
615
|
|
|
331
|
-
###
|
|
616
|
+
### Debug Mode
|
|
332
617
|
|
|
333
618
|
```ruby
|
|
334
|
-
# Enable
|
|
335
|
-
FlowChat.
|
|
619
|
+
# Enable comprehensive debugging
|
|
620
|
+
FlowChat::Config.logger.level = Logger::DEBUG
|
|
621
|
+
|
|
622
|
+
# Add custom debug middleware
|
|
623
|
+
class DebugMiddleware
|
|
624
|
+
def initialize(app)
|
|
625
|
+
@app = app
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def call(context)
|
|
629
|
+
Rails.logger.debug "=== FlowChat Debug ==="
|
|
630
|
+
Rails.logger.debug "Context: #{context.to_h}"
|
|
631
|
+
Rails.logger.debug "Input: #{context.input.inspect}"
|
|
632
|
+
Rails.logger.debug "Session ID: #{context['session.id']}"
|
|
633
|
+
|
|
634
|
+
result = @app.call(context)
|
|
635
|
+
|
|
636
|
+
Rails.logger.debug "Result: #{result.inspect}"
|
|
637
|
+
Rails.logger.debug "======================"
|
|
638
|
+
|
|
639
|
+
result
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
# Use debug middleware
|
|
644
|
+
config.use_middleware DebugMiddleware
|
|
336
645
|
```
|
|
337
646
|
|
|
338
|
-
###
|
|
647
|
+
### Configuration Validation
|
|
339
648
|
|
|
340
649
|
```ruby
|
|
341
|
-
#
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
650
|
+
# Add configuration validation
|
|
651
|
+
class ConfigurationValidator
|
|
652
|
+
def self.validate!
|
|
653
|
+
validate_environment_variables!
|
|
654
|
+
validate_gateway_configuration!
|
|
655
|
+
validate_session_configuration!
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
private
|
|
659
|
+
|
|
660
|
+
def self.validate_environment_variables!
|
|
661
|
+
required_vars = %w[
|
|
662
|
+
WHATSAPP_ACCESS_TOKEN
|
|
663
|
+
WHATSAPP_PHONE_NUMBER_ID
|
|
664
|
+
WHATSAPP_VERIFY_TOKEN
|
|
665
|
+
]
|
|
666
|
+
|
|
667
|
+
missing = required_vars.select { |var| ENV[var].blank? }
|
|
668
|
+
raise "Missing environment variables: #{missing.join(', ')}" if missing.any?
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def self.validate_gateway_configuration!
|
|
672
|
+
# Validate gateway-specific configuration
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
def self.validate_session_configuration!
|
|
676
|
+
# Validate session store connectivity
|
|
677
|
+
FlowChat::Config.cache.write("test_key", "test_value")
|
|
678
|
+
FlowChat::Config.cache.delete("test_key")
|
|
679
|
+
rescue => e
|
|
680
|
+
raise "Session store configuration invalid: #{e.message}"
|
|
681
|
+
end
|
|
348
682
|
end
|
|
349
683
|
|
|
350
|
-
#
|
|
351
|
-
|
|
352
|
-
|
|
684
|
+
# Run validation on startup
|
|
685
|
+
Rails.application.config.after_initialize do
|
|
686
|
+
ConfigurationValidator.validate! if Rails.env.production?
|
|
353
687
|
end
|
|
354
688
|
```
|
|
355
689
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
FlowChat validates configuration at runtime and provides helpful error messages:
|
|
359
|
-
|
|
360
|
-
FlowChat validates configuration at runtime and provides helpful error messages for missing or invalid configurations.
|
|
690
|
+
This configuration guide covers all aspects of FlowChat setup. For platform-specific configuration details, see the individual platform guides.
|