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
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# FlowChat Architecture
|
|
2
|
+
|
|
3
|
+
FlowChat is built around a **composition-based architecture** with **pluggable gateways** that enables unified conversational interfaces across multiple platforms. This document explains the core architectural decisions and how they enable FlowChat's flexibility.
|
|
4
|
+
|
|
5
|
+
## Design Principles
|
|
6
|
+
|
|
7
|
+
### 1. Platform Abstraction
|
|
8
|
+
|
|
9
|
+
FlowChat abstracts platform differences through a unified API, allowing the same flow code to work across USSD, WhatsApp, Telegram, HTTP, and custom platforms.
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# This exact code works on ALL platforms
|
|
13
|
+
def survey_flow
|
|
14
|
+
name = app.screen(:name) { |p| p.ask "What's your name?" }
|
|
15
|
+
rating = app.screen(:rating) { |p| p.select "Rate us:", ["1", "2", "3", "4", "5"] }
|
|
16
|
+
app.say "Thanks #{name}! You rated us #{rating}/5"
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. Composition Over Inheritance
|
|
21
|
+
|
|
22
|
+
Rather than platform-specific classes, FlowChat uses composition to configure behavior:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Old approach (inheritance) - DEPRECATED
|
|
26
|
+
class UssdApp < FlowChat::BaseApp; end
|
|
27
|
+
class WhatsappApp < FlowChat::BaseApp; end
|
|
28
|
+
|
|
29
|
+
# New approach (composition) - CURRENT
|
|
30
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
31
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo # Platform behavior
|
|
32
|
+
config.use_session_store FlowChat::Session::CacheSessionStore # Session behavior
|
|
33
|
+
config.use_middleware CustomLoggingMiddleware # Custom behavior
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 3. Pluggable Gateways
|
|
38
|
+
|
|
39
|
+
Each platform can have multiple gateway implementations, making FlowChat extensible:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# Built-in USSD gateway
|
|
43
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
44
|
+
|
|
45
|
+
# Example custom USSD gateways (you would build these)
|
|
46
|
+
config.use_gateway YourCompany::Ussd::Gateway::Africaist
|
|
47
|
+
config.use_gateway YourCompany::Ussd::Gateway::MTN
|
|
48
|
+
|
|
49
|
+
# Built-in WhatsApp gateway
|
|
50
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
51
|
+
|
|
52
|
+
# Example custom WhatsApp gateways (you would build these)
|
|
53
|
+
config.use_gateway YourCompany::Whatsapp::Gateway::OnPremise
|
|
54
|
+
config.use_gateway YourCompany::Whatsapp::Gateway::Twilio
|
|
55
|
+
|
|
56
|
+
# Example custom platforms (you would build these)
|
|
57
|
+
config.use_gateway YourCompany::Sms::Gateway::Twilio
|
|
58
|
+
config.use_gateway YourCompany::Voice::Gateway::Plivo
|
|
59
|
+
config.use_gateway YourCompany::Slack::Gateway::BoltJS
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Core Components
|
|
63
|
+
|
|
64
|
+
### Processor
|
|
65
|
+
|
|
66
|
+
The `FlowChat::Processor` is the central orchestrator that builds and executes the middleware stack.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
70
|
+
config.use_gateway GatewayClass, *gateway_args
|
|
71
|
+
config.use_session_store SessionStoreClass
|
|
72
|
+
config.use_middleware CustomMiddleware
|
|
73
|
+
config.use_session_config(boundaries: [:flow], identifier: :msisdn)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
processor.run FlowClass, :action_method
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Responsibilities:**
|
|
80
|
+
- Build middleware stack based on configuration
|
|
81
|
+
- Manage session configuration
|
|
82
|
+
- Coordinate gateway and middleware interaction
|
|
83
|
+
- Handle errors and instrumentation
|
|
84
|
+
|
|
85
|
+
### Gateway
|
|
86
|
+
|
|
87
|
+
Gateways handle platform-specific request parsing and response rendering.
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class MyCustomGateway
|
|
91
|
+
def initialize(app, *config_args)
|
|
92
|
+
@app = app
|
|
93
|
+
@config = config_args.first
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def call(context)
|
|
97
|
+
# 1. Parse platform-specific request
|
|
98
|
+
context["request.msisdn"] = extract_phone_number(request)
|
|
99
|
+
context["request.platform"] = :my_platform
|
|
100
|
+
context.input = extract_user_input(request)
|
|
101
|
+
|
|
102
|
+
# 2. Process through middleware stack
|
|
103
|
+
type, prompt, choices, media = @app.call(context)
|
|
104
|
+
|
|
105
|
+
# 3. Render platform-specific response
|
|
106
|
+
send_platform_response(type, prompt, choices, media)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Optional: Configure platform-specific middleware
|
|
110
|
+
def self.configure_middleware_stack(builder, custom_middleware)
|
|
111
|
+
builder.use MyPlatform::SpecialMiddleware
|
|
112
|
+
builder.use custom_middleware
|
|
113
|
+
builder.use MyPlatform::ResponseMiddleware
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Gateway Interface:**
|
|
119
|
+
- `initialize(app, *args)` - Set up gateway with app and config
|
|
120
|
+
- `call(context)` - Process request through middleware stack
|
|
121
|
+
- `self.configure_middleware_stack(builder, custom_middleware)` - Optional middleware configuration
|
|
122
|
+
|
|
123
|
+
### App
|
|
124
|
+
|
|
125
|
+
The `FlowChat::App` provides the unified interface that flows use to interact with users.
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
class FlowChat::App
|
|
129
|
+
def screen(key, &block)
|
|
130
|
+
# Automatic state management and navigation
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def say(message, media: nil)
|
|
134
|
+
# Platform-appropriate message sending
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Context accessors that work across platforms
|
|
138
|
+
def msisdn, user_id, platform, gateway
|
|
139
|
+
def message_id, timestamp, contact_name, location, media
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Key Features:**
|
|
144
|
+
- **Screen-based navigation** with automatic state management
|
|
145
|
+
- **Platform-agnostic prompts** that adapt to each platform's capabilities
|
|
146
|
+
- **Consistent context** regardless of underlying platform
|
|
147
|
+
- **Session integration** for data persistence
|
|
148
|
+
|
|
149
|
+
### Session
|
|
150
|
+
|
|
151
|
+
Session management with configurable boundaries and storage backends.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# Session boundaries control session ID generation
|
|
155
|
+
config.use_session_config(
|
|
156
|
+
boundaries: [:flow, :platform, :gateway, :url], # Session isolation
|
|
157
|
+
identifier: :msisdn, # Session key type
|
|
158
|
+
hash_identifiers: true # Privacy protection
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Session stores control where data is stored
|
|
162
|
+
config.use_session_store FlowChat::Session::CacheSessionStore # Rails sessions
|
|
163
|
+
config.use_session_store FlowChat::Session::CacheSessionStore # Rails cache
|
|
164
|
+
config.use_session_store YourCompany::Session::DatabaseStore # Custom store
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Middleware
|
|
168
|
+
|
|
169
|
+
Extensible processing pipeline for custom logic.
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
class CustomMiddleware
|
|
173
|
+
def initialize(app)
|
|
174
|
+
@app = app
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def call(context)
|
|
178
|
+
# Before processing
|
|
179
|
+
log_request(context)
|
|
180
|
+
|
|
181
|
+
# Process through stack
|
|
182
|
+
result = @app.call(context)
|
|
183
|
+
|
|
184
|
+
# After processing
|
|
185
|
+
log_response(context, result)
|
|
186
|
+
|
|
187
|
+
result
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Request Processing Flow
|
|
193
|
+
|
|
194
|
+
Here's how a request flows through FlowChat:
|
|
195
|
+
|
|
196
|
+
```mermaid
|
|
197
|
+
graph TD
|
|
198
|
+
A[Platform Request] --> B[Gateway]
|
|
199
|
+
B --> C[Parse Request]
|
|
200
|
+
C --> D[Set Context]
|
|
201
|
+
D --> E[Session Middleware]
|
|
202
|
+
E --> F[Custom Middleware]
|
|
203
|
+
F --> G[Executor]
|
|
204
|
+
G --> H[Flow/Action]
|
|
205
|
+
H --> I[App.screen]
|
|
206
|
+
I --> J[Prompt Processing]
|
|
207
|
+
J --> K[Response Generation]
|
|
208
|
+
K --> L[Gateway Response]
|
|
209
|
+
L --> M[Platform Response]
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Detailed Flow
|
|
213
|
+
|
|
214
|
+
1. **Request Arrives** at platform (USSD, WhatsApp, HTTP)
|
|
215
|
+
2. **Gateway** parses platform-specific request format
|
|
216
|
+
3. **Context Setup** with normalized request data
|
|
217
|
+
4. **Session Middleware** manages session state and boundaries
|
|
218
|
+
5. **Custom Middleware** processes business logic
|
|
219
|
+
6. **Executor** instantiates and calls flow
|
|
220
|
+
7. **Flow Method** uses `app.screen()` for conversation logic
|
|
221
|
+
8. **Prompt Processing** handles user input and validation
|
|
222
|
+
9. **Response Generation** creates platform-agnostic response
|
|
223
|
+
10. **Gateway Rendering** converts to platform-specific format
|
|
224
|
+
11. **Platform Response** sent back to user
|
|
225
|
+
|
|
226
|
+
## Middleware Stack
|
|
227
|
+
|
|
228
|
+
The middleware stack is built dynamically based on gateway capabilities:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
def create_middleware_stack
|
|
232
|
+
middleware_stack = ::Middleware::Builder.new(name: @gateway_class.name) do |b|
|
|
233
|
+
# Gateway always comes first
|
|
234
|
+
b.use @gateway_class, *@gateway_args
|
|
235
|
+
|
|
236
|
+
# Session middleware sets up session boundaries
|
|
237
|
+
b.use FlowChat::Session::Middleware, @session_options
|
|
238
|
+
|
|
239
|
+
# Platform-specific middleware (if gateway supports it)
|
|
240
|
+
if @gateway_class.respond_to?(:configure_middleware_stack)
|
|
241
|
+
@gateway_class.configure_middleware_stack(b, custom_middleware)
|
|
242
|
+
else
|
|
243
|
+
b.use custom_middleware
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Executor always goes last
|
|
247
|
+
b.use FlowChat::Executor
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Conditionally inject logger based on configuration
|
|
251
|
+
middleware_stack.inject_logger(FlowChat.logger) if FlowChat::Config.inject_middleware_logger
|
|
252
|
+
|
|
253
|
+
middleware_stack
|
|
254
|
+
end
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Platform-Specific Middleware
|
|
258
|
+
|
|
259
|
+
Gateways can configure their own middleware stack:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
class FlowChat::Ussd::Gateway::Nalo
|
|
263
|
+
def self.configure_middleware_stack(builder, custom_middleware)
|
|
264
|
+
builder.use FlowChat::Ussd::Middleware::Pagination
|
|
265
|
+
builder.use custom_middleware
|
|
266
|
+
builder.use FlowChat::Ussd::Middleware::ChoiceMapper
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
class FlowChat::Whatsapp::Gateway::CloudApi
|
|
271
|
+
def self.configure_middleware_stack(builder, custom_middleware)
|
|
272
|
+
builder.use FlowChat::Whatsapp::Middleware::MediaProcessor
|
|
273
|
+
builder.use custom_middleware
|
|
274
|
+
builder.use FlowChat::Whatsapp::Middleware::TemplateRenderer
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Session Architecture
|
|
280
|
+
|
|
281
|
+
### Session ID Generation
|
|
282
|
+
|
|
283
|
+
Session IDs are generated based on configurable boundaries:
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
# Example session IDs based on boundaries
|
|
287
|
+
boundaries: [:flow] # => "survey_flow:user123"
|
|
288
|
+
boundaries: [:flow, :platform] # => "survey_flow:ussd:user123"
|
|
289
|
+
boundaries: [:flow, :platform, :gateway] # => "survey_flow:ussd:nalo:user123"
|
|
290
|
+
boundaries: [:flow, :url] # => "survey_flow:tenant1.app.com:user123"
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Session Stores
|
|
294
|
+
|
|
295
|
+
Different storage backends for different use cases:
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
# Rails sessions (shorter-lived, good for testing)
|
|
299
|
+
FlowChat::Session::CacheSessionStore
|
|
300
|
+
|
|
301
|
+
# Rails cache (longer-lived, good for production)
|
|
302
|
+
FlowChat::Session::CacheSessionStore
|
|
303
|
+
|
|
304
|
+
# Custom database store (permanent storage)
|
|
305
|
+
class CustomDatabaseStore
|
|
306
|
+
def initialize(context)
|
|
307
|
+
@context = context
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def get(key)
|
|
311
|
+
SessionData.find_by(session_id: @context["session.id"], key: key)&.value
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def set(key, value)
|
|
315
|
+
SessionData.upsert({
|
|
316
|
+
session_id: @context["session.id"],
|
|
317
|
+
key: key,
|
|
318
|
+
value: value
|
|
319
|
+
})
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Extensibility Points
|
|
325
|
+
|
|
326
|
+
FlowChat provides multiple extension points:
|
|
327
|
+
|
|
328
|
+
### 1. Custom Gateways
|
|
329
|
+
|
|
330
|
+
Implement any platform by creating a gateway:
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
class YourCompany::Telegram::Gateway::BotAPI
|
|
334
|
+
def initialize(app, bot_token)
|
|
335
|
+
@app = app
|
|
336
|
+
@bot_token = bot_token
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def call(context)
|
|
340
|
+
# Parse Telegram webhook
|
|
341
|
+
update = JSON.parse(context.controller.request.body.read)
|
|
342
|
+
message = update.dig("message")
|
|
343
|
+
|
|
344
|
+
context["request.user_id"] = message["from"]["id"]
|
|
345
|
+
context["request.platform"] = :telegram
|
|
346
|
+
context.input = message["text"]
|
|
347
|
+
|
|
348
|
+
# Process through stack
|
|
349
|
+
type, prompt, choices, media = @app.call(context)
|
|
350
|
+
|
|
351
|
+
# Send response via Telegram API
|
|
352
|
+
send_telegram_message(prompt, to: context["request.user_id"])
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 2. Custom Middleware
|
|
358
|
+
|
|
359
|
+
Add processing logic at any point:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
class AuthenticationMiddleware
|
|
363
|
+
def call(context)
|
|
364
|
+
user = authenticate_user(context["request.user_id"])
|
|
365
|
+
context["current_user"] = user
|
|
366
|
+
@app.call(context)
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
class RateLimitingMiddleware
|
|
371
|
+
def call(context)
|
|
372
|
+
check_rate_limit(context["request.user_id"])
|
|
373
|
+
@app.call(context)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### 3. Custom Session Stores
|
|
379
|
+
|
|
380
|
+
Implement any storage backend:
|
|
381
|
+
|
|
382
|
+
```ruby
|
|
383
|
+
class RedisSessionStore
|
|
384
|
+
def get(key)
|
|
385
|
+
Redis.current.hget(session_key, key)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def set(key, value)
|
|
389
|
+
Redis.current.hset(session_key, key, value)
|
|
390
|
+
Redis.current.expire(session_key, 24.hours.to_i)
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### 4. Custom Renderers
|
|
396
|
+
|
|
397
|
+
Platform-specific rendering logic:
|
|
398
|
+
|
|
399
|
+
```ruby
|
|
400
|
+
class MyPlatform::Renderer
|
|
401
|
+
def initialize(prompt, choices: nil, media: nil)
|
|
402
|
+
@prompt = prompt
|
|
403
|
+
@choices = choices
|
|
404
|
+
@media = media
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def render
|
|
408
|
+
{
|
|
409
|
+
text: transform_text(@prompt),
|
|
410
|
+
buttons: transform_choices(@choices),
|
|
411
|
+
attachments: transform_media(@media)
|
|
412
|
+
}
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Configuration Architecture
|
|
418
|
+
|
|
419
|
+
FlowChat uses a flexible configuration system:
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
# Global configuration
|
|
423
|
+
FlowChat::Config.logger = Rails.logger
|
|
424
|
+
FlowChat::Config.cache = Rails.cache
|
|
425
|
+
FlowChat::Config.inject_middleware_logger = true # Default: true in Rails development
|
|
426
|
+
|
|
427
|
+
# Platform-specific configuration
|
|
428
|
+
FlowChat::Config.ussd.pagination_page_size = 160
|
|
429
|
+
FlowChat::Config.whatsapp.message_handling_mode = :background
|
|
430
|
+
|
|
431
|
+
# Per-processor configuration
|
|
432
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
433
|
+
config.use_gateway GatewayClass, gateway_config
|
|
434
|
+
config.use_session_config(boundaries: [:flow], identifier: :user_id)
|
|
435
|
+
config.use_middleware CustomMiddleware
|
|
436
|
+
end
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Error Handling & Instrumentation
|
|
440
|
+
|
|
441
|
+
FlowChat includes comprehensive error handling and instrumentation:
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
# Instrumentation events
|
|
445
|
+
FlowChat.instrument(Events::MESSAGE_RECEIVED, {
|
|
446
|
+
from: user_id,
|
|
447
|
+
message: input,
|
|
448
|
+
platform: :ussd
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
FlowChat.instrument(Events::FLOW_EXECUTION_START, {
|
|
452
|
+
flow_name: "survey_flow",
|
|
453
|
+
action: "start"
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
# Error handling in flows
|
|
457
|
+
def payment_flow
|
|
458
|
+
begin
|
|
459
|
+
process_payment(amount)
|
|
460
|
+
rescue PaymentError => e
|
|
461
|
+
app.say "Payment failed: #{e.message}"
|
|
462
|
+
FlowChat.instrument(Events::PAYMENT_FAILED, {
|
|
463
|
+
error: e.message,
|
|
464
|
+
user_id: app.user_id
|
|
465
|
+
})
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Performance Considerations
|
|
471
|
+
|
|
472
|
+
### Memory Efficiency
|
|
473
|
+
|
|
474
|
+
- **Stateless design**: No application state stored in memory
|
|
475
|
+
- **Session isolation**: Users don't affect each other
|
|
476
|
+
- **Lazy loading**: Components loaded only when needed
|
|
477
|
+
|
|
478
|
+
### Scalability
|
|
479
|
+
|
|
480
|
+
- **Horizontal scaling**: Each request is independent
|
|
481
|
+
- **Background processing**: Long-running tasks handled asynchronously
|
|
482
|
+
- **Caching**: Session data cached for performance
|
|
483
|
+
|
|
484
|
+
### Network Efficiency
|
|
485
|
+
|
|
486
|
+
- **Platform optimization**: Each gateway optimizes for its platform
|
|
487
|
+
- **Compression**: Large responses automatically paginated (USSD)
|
|
488
|
+
- **Batching**: Multiple messages combined when possible (WhatsApp)
|
|
489
|
+
|
|
490
|
+
## Security
|
|
491
|
+
|
|
492
|
+
### Session Security
|
|
493
|
+
|
|
494
|
+
- **Identifier hashing**: Phone numbers hashed by default
|
|
495
|
+
- **Session boundaries**: Prevent cross-tenant data access
|
|
496
|
+
- **Secure storage**: Session stores can encrypt data
|
|
497
|
+
|
|
498
|
+
### Input Validation
|
|
499
|
+
|
|
500
|
+
- **Automatic sanitization**: User input sanitized by default
|
|
501
|
+
- **Validation pipelines**: Custom validation in prompts
|
|
502
|
+
- **Type safety**: Input transformation prevents type errors
|
|
503
|
+
|
|
504
|
+
### Platform Security
|
|
505
|
+
|
|
506
|
+
- **Signature validation**: Webhook signatures verified
|
|
507
|
+
- **Rate limiting**: Built-in protection against abuse
|
|
508
|
+
- **Access controls**: Gateway-level authentication
|
|
509
|
+
|
|
510
|
+
This architecture enables FlowChat to be both powerful and flexible, supporting current platforms while being easily extensible for future platforms and use cases.
|