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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.cliff.toml +74 -0
  3. data/.github/workflows/ci.yml +2 -3
  4. data/.github/workflows/release.yml +56 -0
  5. data/.standard.yml +4 -0
  6. data/CHANGELOG.md +22 -0
  7. data/CLAUDE.md +327 -0
  8. data/CONTRIBUTING.md +134 -0
  9. data/Gemfile +1 -0
  10. data/README.md +290 -105
  11. data/Rakefile +5 -1
  12. data/SECURITY.md +42 -349
  13. data/docs/architecture.md +510 -0
  14. data/docs/async-background-processing.md +298 -0
  15. data/docs/configuration.md +556 -226
  16. data/docs/factory-pattern.md +355 -0
  17. data/docs/gateway-context-variables.md +171 -0
  18. data/docs/gateway-development.md +723 -0
  19. data/docs/getting-started.md +429 -0
  20. data/docs/instrumentation.md +264 -153
  21. data/docs/platforms/telegram.md +1013 -0
  22. data/docs/platforms/ussd.md +693 -0
  23. data/docs/platforms/whatsapp.md +1395 -0
  24. data/docs/testing.md +243 -365
  25. data/examples/custom_session_id_example.rb +119 -0
  26. data/examples/http_controller.rb +11 -11
  27. data/examples/intercom_configuration_example.rb +118 -0
  28. data/examples/intercom_controller.rb +194 -0
  29. data/examples/multi_tenant_whatsapp_controller.rb +4 -4
  30. data/examples/ussd_controller.rb +9 -9
  31. data/examples/whatsapp_controller.rb +2 -2
  32. data/flow_chat.gemspec +4 -0
  33. data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
  34. data/lib/flow_chat/async_job.rb +176 -0
  35. data/lib/flow_chat/config.rb +10 -30
  36. data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
  37. data/lib/flow_chat/factory.rb +94 -0
  38. data/lib/flow_chat/gateway_async_support.rb +106 -0
  39. data/lib/flow_chat/generic_async_job.rb +30 -0
  40. data/lib/flow_chat/http/gateway/simple.rb +81 -33
  41. data/lib/flow_chat/http/renderer.rb +3 -3
  42. data/lib/flow_chat/instrumentation/setup.rb +1 -1
  43. data/lib/flow_chat/instrumentation.rb +23 -0
  44. data/lib/flow_chat/intercom/client.rb +155 -0
  45. data/lib/flow_chat/intercom/configuration.rb +149 -0
  46. data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
  47. data/lib/flow_chat/intercom/renderer.rb +71 -0
  48. data/lib/flow_chat/phone_number_util.rb +37 -35
  49. data/lib/flow_chat/processor.rb +188 -0
  50. data/lib/flow_chat/renderers/markdown_support.rb +58 -0
  51. data/lib/flow_chat/session/middleware.rb +25 -9
  52. data/lib/flow_chat/telegram/client.rb +240 -0
  53. data/lib/flow_chat/telegram/configuration.rb +118 -0
  54. data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
  55. data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
  56. data/lib/flow_chat/telegram/renderer.rb +125 -0
  57. data/lib/flow_chat/telegram.rb +7 -0
  58. data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
  59. data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
  60. data/lib/flow_chat/ussd/renderer.rb +1 -1
  61. data/lib/flow_chat/version.rb +1 -1
  62. data/lib/flow_chat/whatsapp/client.rb +144 -13
  63. data/lib/flow_chat/whatsapp/configuration.rb +1 -1
  64. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
  65. data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
  66. data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
  67. data/lib/flow_chat/whatsapp/renderer.rb +145 -11
  68. data/lib/flow_chat.rb +11 -1
  69. data/lib/tasks/release.rake +165 -0
  70. metadata +84 -25
  71. data/docs/flows.md +0 -320
  72. data/docs/http-gateway-protocol.md +0 -432
  73. data/docs/images/simulator.png +0 -0
  74. data/docs/media.md +0 -153
  75. data/docs/sessions.md +0 -433
  76. data/docs/ussd-setup.md +0 -322
  77. data/docs/whatsapp-setup.md +0 -162
  78. data/examples/whatsapp_message_job.rb +0 -113
  79. data/lib/flow_chat/base_processor.rb +0 -146
  80. data/lib/flow_chat/http/app.rb +0 -6
  81. data/lib/flow_chat/http/middleware/executor.rb +0 -24
  82. data/lib/flow_chat/http/processor.rb +0 -33
  83. data/lib/flow_chat/session/rails_session_store.rb +0 -68
  84. data/lib/flow_chat/ussd/app.rb +0 -6
  85. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
  86. data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
  87. data/lib/flow_chat/ussd/processor.rb +0 -39
  88. data/lib/flow_chat/whatsapp/app.rb +0 -29
  89. data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
  90. data/lib/flow_chat/whatsapp/processor.rb +0 -32
  91. 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.