flow_chat 0.9.0 → 0.10.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/.github/workflows/pages.yml +43 -0
- data/CHANGELOG.md +26 -0
- data/README.md +166 -295
- data/Rakefile +12 -1
- data/SECURITY.md +1 -1
- data/docs/architecture.md +52 -479
- data/docs/async-background-processing.md +31 -265
- data/docs/configuration.md +106 -613
- data/docs/factory-pattern.md +27 -324
- data/docs/gateway-context-variables.md +140 -143
- data/docs/gateway-development.md +86 -650
- data/docs/getting-started.md +40 -379
- data/docs/instrumentation.md +88 -279
- data/docs/platforms/instagram.md +278 -0
- data/docs/platforms/messenger.md +205 -0
- data/docs/platforms/telegram.md +47 -951
- data/docs/platforms/ussd.md +38 -653
- data/docs/platforms/whatsapp.md +73 -1321
- data/docs/superpowers/plans/2026-07-09-inbound-media-support.md +732 -0
- data/docs/superpowers/plans/2026-07-09-inbound-media-support.md.tasks.json +58 -0
- data/docs/superpowers/plans/2026-08-10-messenger-instagram.md +4064 -0
- data/docs/superpowers/plans/2026-08-10-messenger-instagram.md.tasks.json +226 -0
- data/docs/superpowers/plans/2026-08-16-unified-choice-resolution.md +972 -0
- data/docs/superpowers/plans/2026-08-16-unified-choice-resolution.md.tasks.json +88 -0
- data/docs/superpowers/specs/2026-07-09-inbound-media-support-design.md +195 -0
- data/docs/superpowers/specs/2026-08-10-messenger-instagram-design.md +391 -0
- data/docs/testing.md +26 -297
- data/examples/http_controller.rb +12 -10
- data/examples/intercom_configuration_example.rb +19 -24
- data/examples/intercom_controller.rb +8 -20
- data/examples/multi_tenant_whatsapp_controller.rb +61 -166
- data/examples/simulator_controller.rb +0 -1
- data/examples/ussd_controller.rb +86 -158
- data/examples/whatsapp_controller.rb +16 -15
- data/examples/whatsapp_media_examples.rb +27 -79
- data/lib/flow_chat/app.rb +129 -11
- data/lib/flow_chat/choice_titles.rb +95 -0
- data/lib/flow_chat/config.rb +124 -1
- data/lib/flow_chat/delivery_error.rb +9 -0
- data/lib/flow_chat/http/configuration_error.rb +9 -0
- data/lib/flow_chat/http/gateway/simple.rb +24 -4
- data/lib/flow_chat/http/middleware/choice_mapper.rb +94 -0
- data/lib/flow_chat/input.rb +86 -0
- data/lib/flow_chat/instagram/client.rb +32 -0
- data/lib/flow_chat/instagram/configuration.rb +147 -0
- data/lib/flow_chat/instagram/configuration_error.rb +7 -0
- data/lib/flow_chat/instagram/gateway/send_api.rb +63 -0
- data/lib/flow_chat/instagram/middleware/choice_mapper.rb +22 -0
- data/lib/flow_chat/instagram/renderer.rb +23 -0
- data/lib/flow_chat/instrumentation/metrics_collector.rb +6 -1
- data/lib/flow_chat/instrumentation.rb +160 -1
- data/lib/flow_chat/intercom/client.rb +34 -28
- data/lib/flow_chat/intercom/configuration.rb +2 -49
- data/lib/flow_chat/intercom/configuration_error.rb +9 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +81 -57
- data/lib/flow_chat/intercom/middleware/choice_mapper.rb +101 -0
- data/lib/flow_chat/intercom/renderer.rb +57 -5
- data/lib/flow_chat/media.rb +121 -0
- data/lib/flow_chat/messenger/client.rb +264 -0
- data/lib/flow_chat/messenger/configuration.rb +103 -0
- data/lib/flow_chat/messenger/configuration_error.rb +9 -0
- data/lib/flow_chat/messenger/gateway/send_api.rb +42 -0
- data/lib/flow_chat/messenger/middleware/choice_mapper.rb +185 -0
- data/lib/flow_chat/messenger/renderer.rb +150 -0
- data/lib/flow_chat/meta/challenge.rb +24 -0
- data/lib/flow_chat/meta/choice_ladder.rb +37 -0
- data/lib/flow_chat/meta/configuration_error.rb +7 -0
- data/lib/flow_chat/meta/gateway_identity.rb +38 -0
- data/lib/flow_chat/meta/messaging_gateway.rb +468 -0
- data/lib/flow_chat/meta/signature.rb +30 -0
- data/lib/flow_chat/meta/signature_validation.rb +66 -0
- data/lib/flow_chat/meta/webhook_verification.rb +43 -0
- data/lib/flow_chat/named_configuration.rb +65 -0
- data/lib/flow_chat/prompt.rb +13 -16
- data/lib/flow_chat/renderers/markdown_support.rb +109 -0
- data/lib/flow_chat/security.rb +76 -0
- data/lib/flow_chat/session/middleware.rb +11 -2
- data/lib/flow_chat/simulator/controller.rb +31 -15
- data/lib/flow_chat/simulator/views/simulator.html.erb +184 -20
- data/lib/flow_chat/telegram/client.rb +47 -4
- data/lib/flow_chat/telegram/configuration.rb +2 -42
- data/lib/flow_chat/telegram/configuration_error.rb +9 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +46 -28
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +77 -16
- data/lib/flow_chat/telegram/renderer.rb +10 -2
- data/lib/flow_chat/text_truncator.rb +75 -0
- data/lib/flow_chat/ussd/middleware/choice_mapper.rb +10 -0
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +14 -7
- data/lib/flow_chat/whatsapp/configuration.rb +12 -51
- data/lib/flow_chat/whatsapp/configuration_error.rb +9 -0
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +254 -203
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +144 -57
- data/lib/flow_chat/whatsapp/renderer.rb +121 -60
- data/lib/flow_chat.rb +0 -10
- data/site/.nojekyll +0 -0
- data/site/.og-card.html +89 -0
- data/site/favicon.svg +6 -0
- data/site/index.html +209 -0
- data/site/og.png +0 -0
- metadata +51 -3
- data/lib/flow_chat/whatsapp/id_generator.rb +0 -124
data/docs/testing.md
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
|
-
# Testing
|
|
1
|
+
# Testing
|
|
2
2
|
|
|
3
|
-
FlowChat
|
|
3
|
+
FlowChat gives you two ways to exercise a flow without a live gateway: a web simulator for manual, interactive testing during development, and the HTTP gateway for automated request tests.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## The simulator
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The simulator is a web page that drives your flows the way a real platform would, so you can step through a conversation from the browser. It works the same regardless of which gateway a flow targets.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Enable it in two steps.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- **Real-Time Request Logging**: Complete HTTP traffic monitoring and debugging
|
|
13
|
-
- **Security**: HMAC-signed cookie authentication
|
|
14
|
-
- **Platform-Specific Rendering**: Accurate simulation of each platform's behavior
|
|
15
|
-
- **Configuration Management**: Test multiple endpoints from a single interface
|
|
16
|
-
|
|
17
|
-
### Setup & Configuration
|
|
18
|
-
|
|
19
|
-
#### 1. Basic Configuration
|
|
11
|
+
Set the simulator secret during boot:
|
|
20
12
|
|
|
21
13
|
```ruby
|
|
22
14
|
# config/initializers/flow_chat.rb
|
|
23
|
-
FlowChat::Config.simulator_secret =
|
|
15
|
+
FlowChat::Config.simulator_secret = Rails.application.credentials.flow_chat_simulator_secret
|
|
24
16
|
```
|
|
25
17
|
|
|
26
|
-
|
|
18
|
+
The secret gates access. The simulator controller signs a cookie with it, and the gateways only enter simulator mode when that cookie is valid, so the simulator stays off in any environment where the secret is unset.
|
|
19
|
+
|
|
20
|
+
Mount a controller that includes the simulator module and lists the endpoints to test:
|
|
27
21
|
|
|
28
22
|
```ruby
|
|
29
23
|
# app/controllers/simulator_controller.rb
|
|
@@ -36,318 +30,53 @@ class SimulatorController < ApplicationController
|
|
|
36
30
|
|
|
37
31
|
protected
|
|
38
32
|
|
|
39
|
-
# Configure available test endpoints
|
|
40
33
|
def configurations
|
|
41
34
|
{
|
|
42
35
|
ussd_main: {
|
|
43
|
-
name: "USSD
|
|
44
|
-
description: "USSD integration using Nalo gateway",
|
|
36
|
+
name: "USSD",
|
|
45
37
|
processor_type: "ussd",
|
|
46
38
|
gateway: "nalo",
|
|
47
|
-
endpoint: "/ussd"
|
|
48
|
-
icon: "📱",
|
|
49
|
-
color: "#28a745",
|
|
50
|
-
settings: {
|
|
51
|
-
phone_number: default_phone_number,
|
|
52
|
-
session_timeout: 300
|
|
53
|
-
}
|
|
39
|
+
endpoint: "/ussd"
|
|
54
40
|
},
|
|
55
41
|
whatsapp_main: {
|
|
56
|
-
name: "WhatsApp
|
|
57
|
-
description: "WhatsApp integration using Cloud API",
|
|
42
|
+
name: "WhatsApp",
|
|
58
43
|
processor_type: "whatsapp",
|
|
59
|
-
gateway: "cloud_api",
|
|
60
|
-
endpoint: "/whatsapp/webhook"
|
|
61
|
-
icon: "💬",
|
|
62
|
-
color: "#25D366",
|
|
63
|
-
settings: {
|
|
64
|
-
phone_number: default_phone_number,
|
|
65
|
-
contact_name: default_contact_name
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
http_api: {
|
|
69
|
-
name: "HTTP API",
|
|
70
|
-
description: "JSON HTTP API endpoint",
|
|
71
|
-
processor_type: "http",
|
|
72
|
-
gateway: "http_simple",
|
|
73
|
-
endpoint: "/http/webhook",
|
|
74
|
-
icon: "🌐",
|
|
75
|
-
color: "#0066cc",
|
|
76
|
-
settings: {
|
|
77
|
-
user_id: default_phone_number
|
|
78
|
-
}
|
|
44
|
+
gateway: "cloud_api",
|
|
45
|
+
endpoint: "/whatsapp/webhook"
|
|
79
46
|
}
|
|
80
47
|
}
|
|
81
48
|
end
|
|
82
|
-
|
|
83
|
-
def default_phone_number
|
|
84
|
-
"+233244123456"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def default_contact_name
|
|
88
|
-
"John Doe"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def default_config_key
|
|
92
|
-
:ussd_main
|
|
93
|
-
end
|
|
94
49
|
end
|
|
95
50
|
```
|
|
96
51
|
|
|
97
|
-
#### 3. Add Route
|
|
98
|
-
|
|
99
52
|
```ruby
|
|
100
53
|
# config/routes.rb
|
|
101
|
-
|
|
102
|
-
get '/simulator' => 'simulator#index'
|
|
103
|
-
# ... other routes
|
|
104
|
-
end
|
|
54
|
+
get "/simulator", to: "simulator#index"
|
|
105
55
|
```
|
|
106
56
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
#### HMAC-Signed Cookies
|
|
110
|
-
|
|
111
|
-
The simulator uses secure, timestamped cookies for authentication:
|
|
112
|
-
|
|
113
|
-
```ruby
|
|
114
|
-
# Automatic cookie generation in simulator controller
|
|
115
|
-
def set_simulator_cookie
|
|
116
|
-
simulator_secret = FlowChat::Config.simulator_secret
|
|
117
|
-
timestamp = Time.now.to_i
|
|
118
|
-
message = "simulator:#{timestamp}"
|
|
119
|
-
signature = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), simulator_secret, message)
|
|
120
|
-
|
|
121
|
-
cookies[:flowchat_simulator] = {
|
|
122
|
-
value: "#{timestamp}:#{signature}",
|
|
123
|
-
expires: 24.hours.from_now,
|
|
124
|
-
secure: request.ssl?, # HTTPS only in production
|
|
125
|
-
httponly: true, # Prevent XSS
|
|
126
|
-
same_site: :lax # CSRF protection
|
|
127
|
-
}
|
|
128
|
-
end
|
|
129
|
-
```
|
|
57
|
+
Each entry in `configurations` points at one of your real webhook endpoints, so the simulator posts to the same controller actions the platform would. Open `/simulator`, pick an endpoint, and send messages.
|
|
130
58
|
|
|
131
|
-
|
|
59
|
+
## Automated tests
|
|
132
60
|
|
|
133
|
-
|
|
61
|
+
For request specs, drive a flow through the HTTP gateway (`FlowChat::Http::Gateway::Simple`), which speaks plain JSON. Point a controller at your flow:
|
|
134
62
|
|
|
135
63
|
```ruby
|
|
136
|
-
|
|
137
|
-
enable_simulator: Rails.env.local? # true for development/test
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Platform-Specific Features
|
|
141
|
-
|
|
142
|
-
#### USSD Testing
|
|
143
|
-
|
|
144
|
-
**Interface**: Menu-style display with character limits and pagination
|
|
145
|
-
|
|
146
|
-
**Features**:
|
|
147
|
-
- Network-specific character limits (MTN: 160, Airtel: 140, etc.)
|
|
148
|
-
- Session timeout simulation
|
|
149
|
-
- Menu option selection
|
|
150
|
-
- Automatic pagination for long content
|
|
151
|
-
|
|
152
|
-
**Request Format**:
|
|
153
|
-
```ruby
|
|
154
|
-
{
|
|
155
|
-
msisdn: "+233244123456",
|
|
156
|
-
text: "user_input",
|
|
157
|
-
session_id: "session_123",
|
|
158
|
-
network: "mtn"
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
#### WhatsApp Testing
|
|
163
|
-
|
|
164
|
-
**Interface**: Chat-style interface with contact avatars and message history
|
|
165
|
-
|
|
166
|
-
**Features**:
|
|
167
|
-
- Contact name and avatar display
|
|
168
|
-
- Interactive buttons and lists
|
|
169
|
-
- Media support (images, documents, audio, video)
|
|
170
|
-
- Cloud API webhook simulation
|
|
171
|
-
|
|
172
|
-
**Request Format**:
|
|
173
|
-
```javascript
|
|
174
|
-
{
|
|
175
|
-
simulator_mode: true,
|
|
176
|
-
entry: [{
|
|
177
|
-
changes: [{
|
|
178
|
-
value: {
|
|
179
|
-
messaging_product: "whatsapp",
|
|
180
|
-
metadata: {
|
|
181
|
-
display_phone_number: "233244123456",
|
|
182
|
-
phone_number_id: "phone_number_id_123"
|
|
183
|
-
},
|
|
184
|
-
messages: [{
|
|
185
|
-
text: { body: "user_input" },
|
|
186
|
-
type: 'text'
|
|
187
|
-
}],
|
|
188
|
-
contacts: [{
|
|
189
|
-
profile: { name: "John Doe" },
|
|
190
|
-
wa_id: "233244123456"
|
|
191
|
-
}]
|
|
192
|
-
}
|
|
193
|
-
}]
|
|
194
|
-
}]
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Response Handling**:
|
|
199
|
-
```javascript
|
|
200
|
-
// Simulator expects JSON response for full simulation
|
|
201
|
-
{
|
|
202
|
-
mode: "simulator",
|
|
203
|
-
webhook_processed: true,
|
|
204
|
-
would_send: {
|
|
205
|
-
to: "233244123456",
|
|
206
|
-
type: "text",
|
|
207
|
-
text: { body: "Response message" }
|
|
208
|
-
},
|
|
209
|
-
message_info: {
|
|
210
|
-
to: "233244123456",
|
|
211
|
-
contact_name: "John Doe",
|
|
212
|
-
timestamp: "2024-01-01T12:00:00Z"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
#### HTTP Testing
|
|
218
|
-
|
|
219
|
-
**Interface**: API-style interface showing JSON request/response
|
|
220
|
-
|
|
221
|
-
**Features**:
|
|
222
|
-
- JSON payload display
|
|
223
|
-
- Session management
|
|
224
|
-
- Custom headers
|
|
225
|
-
- Error handling
|
|
226
|
-
|
|
227
|
-
**Request Format**:
|
|
228
|
-
```javascript
|
|
229
|
-
{
|
|
230
|
-
session_id: "session_123",
|
|
231
|
-
user_id: "+233244123456",
|
|
232
|
-
input: "user_input",
|
|
233
|
-
simulator_mode: true
|
|
234
|
-
}
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Request Logging & Debugging
|
|
238
|
-
|
|
239
|
-
#### Real-Time Traffic Monitoring
|
|
240
|
-
|
|
241
|
-
The simulator provides comprehensive request logging:
|
|
242
|
-
|
|
243
|
-
```javascript
|
|
244
|
-
// Automatic logging of all HTTP traffic
|
|
245
|
-
addRequestLog('POST', endpoint, requestData, responseData, statusCode, errorMessage)
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
**Log Information**:
|
|
249
|
-
- Request method and URL
|
|
250
|
-
- Complete request payload
|
|
251
|
-
- Response status and data
|
|
252
|
-
- Timestamps and duration
|
|
253
|
-
- Error messages with helpful debugging hints
|
|
254
|
-
|
|
255
|
-
#### Error Handling
|
|
256
|
-
|
|
257
|
-
The simulator provides detailed error messages for common issues:
|
|
258
|
-
|
|
259
|
-
- **Connection Failures**: CORS, SSL, network issues
|
|
260
|
-
- **404 Not Found**: Route configuration problems
|
|
261
|
-
- **500 Server Errors**: Application errors
|
|
262
|
-
- **Authentication Issues**: Invalid simulator cookies
|
|
263
|
-
|
|
264
|
-
### Enabling Simulator Mode in Your Endpoints
|
|
265
|
-
|
|
266
|
-
#### For WhatsApp Endpoints
|
|
267
|
-
|
|
268
|
-
Your WhatsApp processor automatically handles simulator mode:
|
|
269
|
-
|
|
270
|
-
```ruby
|
|
271
|
-
class WhatsappController < ApplicationController
|
|
272
|
-
def webhook
|
|
273
|
-
processor = FlowCha::Processor.new(self, enable_simulator: !Rails.env.production?) do |config|
|
|
274
|
-
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
275
|
-
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
processor.run WelcomeFlow, :main_page
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
#### For HTTP Endpoints
|
|
284
|
-
|
|
285
|
-
HTTP endpoints work automatically with the simulator:
|
|
286
|
-
|
|
287
|
-
```ruby
|
|
288
|
-
class HttpController < ApplicationController
|
|
64
|
+
class ChatController < ApplicationController
|
|
289
65
|
def webhook
|
|
290
66
|
processor = FlowChat::Processor.new(self) do |config|
|
|
291
67
|
config.use_gateway FlowChat::Http::Gateway::Simple
|
|
292
68
|
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
293
69
|
end
|
|
294
|
-
|
|
295
|
-
processor.run WelcomeFlow, :main_page
|
|
70
|
+
processor.run RegistrationFlow, :main_page
|
|
296
71
|
end
|
|
297
72
|
end
|
|
298
73
|
```
|
|
299
74
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
#### 1. Start the Simulator
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
rails server
|
|
306
|
-
# Visit http://localhost:3000/simulator
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
#### 2. Configure Test Environment
|
|
310
|
-
|
|
311
|
-
- Select endpoint from configuration dropdown
|
|
312
|
-
- Set phone number and contact name
|
|
313
|
-
- Choose platform type (USSD/WhatsApp/HTTP)
|
|
314
|
-
|
|
315
|
-
#### 3. Test Conversation Flows
|
|
316
|
-
|
|
317
|
-
- Click "Start Session" to begin
|
|
318
|
-
- Send messages through the platform-specific interface
|
|
319
|
-
- Monitor request/response logs in real-time
|
|
320
|
-
- Reset session to test different scenarios
|
|
321
|
-
|
|
322
|
-
#### 4. Debug Issues
|
|
323
|
-
|
|
324
|
-
- Check request logs for HTTP traffic details
|
|
325
|
-
- Verify endpoint responses and status codes
|
|
326
|
-
- Review error messages for configuration issues
|
|
327
|
-
- Test different platforms and configurations
|
|
328
|
-
|
|
329
|
-
### Best Practices
|
|
330
|
-
|
|
331
|
-
#### Security
|
|
332
|
-
|
|
333
|
-
- Always set `FlowChat::Config.simulator_secret` in production
|
|
334
|
-
- Use secure cookies over HTTPS in production
|
|
335
|
-
- Restrict simulator access to development/staging environments
|
|
336
|
-
|
|
337
|
-
#### Testing Strategy
|
|
338
|
-
|
|
339
|
-
1. **Start with Simulator**: Test flows before gateway integration
|
|
340
|
-
2. **Test All Platforms**: Verify behavior across USSD, WhatsApp, HTTP
|
|
341
|
-
3. **Check Character Limits**: Test USSD character restrictions
|
|
342
|
-
4. **Verify Session Management**: Test session boundaries and persistence
|
|
343
|
-
5. **Monitor Request Logs**: Debug integration issues in real-time
|
|
344
|
-
|
|
345
|
-
#### Performance
|
|
346
|
-
|
|
347
|
-
- Use simulator for rapid prototyping and debugging
|
|
348
|
-
- Test edge cases and error conditions safely
|
|
349
|
-
- Validate webhook payloads before production deployment
|
|
75
|
+
The Simple gateway expects `session_id` and `user_id` in the request and returns JSON with the prompt, choices, and any media. Post the next input with the same `session_id` to advance the conversation, and assert on the returned message and choices. Set `FlowChat::Config.cache` to a real store (for example `ActiveSupport::Cache::MemoryStore.new`) in your test setup so sessions persist across the requests in a single test.
|
|
350
76
|
|
|
351
|
-
|
|
77
|
+
Because one flow behaves the same across platforms, a flow verified through the HTTP gateway behaves the same on USSD, WhatsApp, and Telegram, apart from each platform's rendering.
|
|
352
78
|
|
|
79
|
+
## Related
|
|
353
80
|
|
|
81
|
+
- [Getting started](getting-started.md)
|
|
82
|
+
- [Configuration](configuration.md)
|
data/examples/http_controller.rb
CHANGED
|
@@ -33,20 +33,22 @@ class HttpController < ApplicationController
|
|
|
33
33
|
config.use_gateway FlowChat::Http::Gateway::Simple
|
|
34
34
|
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
35
35
|
|
|
36
|
-
#
|
|
36
|
+
# The HTTP gateway sets request.user_id from the request's user_id, so key
|
|
37
|
+
# sessions on it. (:msisdn would be nil here, collapsing every caller into
|
|
38
|
+
# one session.)
|
|
37
39
|
config.use_session_config(
|
|
38
40
|
boundaries: [:flow, :platform],
|
|
39
41
|
hash_identifiers: true,
|
|
40
|
-
identifier: :
|
|
42
|
+
identifier: :user_id
|
|
41
43
|
)
|
|
42
44
|
end
|
|
43
45
|
|
|
44
|
-
processor.run
|
|
46
|
+
processor.run HttpWelcomeFlow, :main_page
|
|
45
47
|
end
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
# Example flow for HTTP gateway
|
|
49
|
-
class
|
|
51
|
+
class HttpWelcomeFlow < FlowChat::Flow
|
|
50
52
|
def main_page
|
|
51
53
|
name = app.screen(:name) do |prompt|
|
|
52
54
|
prompt.ask "Hello! What's your name?",
|
|
@@ -87,7 +89,7 @@ class WelcomeFlow < FlowChat::Flow
|
|
|
87
89
|
if continue
|
|
88
90
|
features_demo
|
|
89
91
|
else
|
|
90
|
-
app.say "Thanks for trying FlowChat HTTP
|
|
92
|
+
app.say "Thanks for trying the FlowChat HTTP gateway!"
|
|
91
93
|
end
|
|
92
94
|
end
|
|
93
95
|
|
|
@@ -115,7 +117,7 @@ class WelcomeFlow < FlowChat::Flow
|
|
|
115
117
|
def media_demo
|
|
116
118
|
app.say "FlowChat supports rich media in HTTP responses!",
|
|
117
119
|
media: {
|
|
118
|
-
url: "https://
|
|
120
|
+
url: "https://example.com/images/flowchat-http-demo.png",
|
|
119
121
|
type: :image,
|
|
120
122
|
caption: "FlowChat HTTP Gateway Demo"
|
|
121
123
|
}
|
|
@@ -146,9 +148,9 @@ class WelcomeFlow < FlowChat::Flow
|
|
|
146
148
|
timestamp = app.session.get("demo_timestamp")
|
|
147
149
|
|
|
148
150
|
app.say "Session Demo:"
|
|
149
|
-
app.say "
|
|
150
|
-
app.say "
|
|
151
|
-
app.say "
|
|
152
|
-
app.say "
|
|
151
|
+
app.say "This is visit ##{counter} in this session"
|
|
152
|
+
app.say "Session started at: #{timestamp}"
|
|
153
|
+
app.say "Session data persists across HTTP requests"
|
|
154
|
+
app.say "Session ID: #{app.context["session.id"]}"
|
|
153
155
|
end
|
|
154
156
|
end
|
|
@@ -21,15 +21,17 @@
|
|
|
21
21
|
# 3. Named Configuration Example (for multi-tenant apps)
|
|
22
22
|
class MultiTenantIntercomSetup
|
|
23
23
|
def self.setup_configurations
|
|
24
|
-
#
|
|
25
|
-
|
|
24
|
+
# Passing a name registers the configuration under that name, so you can
|
|
25
|
+
# fetch it later with Configuration.get(:main). Set attributes with tap;
|
|
26
|
+
# Configuration.new does not yield a block.
|
|
27
|
+
FlowChat::Intercom::Configuration.new("main").tap do |config|
|
|
26
28
|
config.access_token = Rails.application.credentials.dig(:intercom, :main, :access_token)
|
|
27
29
|
config.client_secret = Rails.application.credentials.dig(:intercom, :main, :client_secret)
|
|
28
30
|
config.admin_id = Rails.application.credentials.dig(:intercom, :main, :admin_id)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
# Enterprise customer configuration
|
|
32
|
-
FlowChat::Intercom::Configuration.new("enterprise") do |config|
|
|
34
|
+
FlowChat::Intercom::Configuration.new("enterprise").tap do |config|
|
|
33
35
|
config.access_token = Rails.application.credentials.dig(:intercom, :enterprise, :access_token)
|
|
34
36
|
config.client_secret = Rails.application.credentials.dig(:intercom, :enterprise, :client_secret)
|
|
35
37
|
config.admin_id = Rails.application.credentials.dig(:intercom, :enterprise, :admin_id)
|
|
@@ -53,17 +55,17 @@ class TenantAwareIntercomController < ApplicationController
|
|
|
53
55
|
skip_forgery_protection
|
|
54
56
|
|
|
55
57
|
def webhook
|
|
56
|
-
#
|
|
58
|
+
# Fetch the named configuration registered in setup_configurations above.
|
|
57
59
|
tenant = params[:tenant] || "main"
|
|
58
|
-
FlowChat::Intercom::Configuration.get(tenant)
|
|
60
|
+
tenant_config = FlowChat::Intercom::Configuration.get(tenant)
|
|
59
61
|
|
|
60
62
|
processor = FlowChat::Processor.new(self) do |config|
|
|
61
|
-
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi,
|
|
63
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, tenant_config
|
|
62
64
|
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
)
|
|
65
|
+
# request.id is the conversation id (the default :request_id identifier),
|
|
66
|
+
# so each conversation gets its own session. The :url boundary separates
|
|
67
|
+
# tenants that share the flow.
|
|
68
|
+
config.use_session_config(boundaries: [:flow, :url])
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
processor.run CustomerSupportFlow, :handle_conversation
|
|
@@ -95,24 +97,17 @@ end
|
|
|
95
97
|
# - Check logs for any signature validation issues
|
|
96
98
|
|
|
97
99
|
# 7. Finding Your Admin ID (Required)
|
|
98
|
-
#
|
|
100
|
+
# The admin_id identifies which admin sends messages to a conversation.
|
|
101
|
+
# FlowChat's own client only sends messages, so list admins with the official
|
|
102
|
+
# intercom gem in a Rails console:
|
|
99
103
|
#
|
|
100
104
|
# rails console
|
|
101
105
|
# ```
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
# result = client.list_admins
|
|
105
|
-
# result["admins"].each { |admin| puts "#{admin["name"]} (#{admin["email"]}) - ID: #{admin["id"]}" }
|
|
106
|
+
# intercom = Intercom::Client.new(token: Rails.application.credentials.dig(:intercom, :access_token))
|
|
107
|
+
# intercom.admins.all.each { |admin| puts "#{admin.name} (#{admin.email}) - ID: #{admin.id}" }
|
|
106
108
|
# ```
|
|
107
109
|
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
# Alternative methods:
|
|
111
|
-
# 1. Go to your Intercom Developer Hub (https://developers.intercom.com/)
|
|
112
|
-
# 2. Navigate to your app → Configure → Basic Information
|
|
113
|
-
# 3. Find the Admin ID in the app details
|
|
114
|
-
#
|
|
115
|
-
# The admin_id is required for sending messages from FlowChat to Intercom conversations.
|
|
116
|
-
# The API uses this to identify which admin is sending the message.
|
|
110
|
+
# Copy the ID of the admin you want to send from. You can also find it in the
|
|
111
|
+
# Intercom Developer Hub under your app's Configure -> Basic Information.
|
|
117
112
|
#
|
|
118
113
|
# Note: The old bot_user_id configuration is no longer needed.
|
|
@@ -14,11 +14,10 @@ class IntercomController < ApplicationController
|
|
|
14
14
|
# Use cache-based session storage for longer-lived sessions
|
|
15
15
|
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
17
|
+
# The Intercom gateway sets request.id to the conversation id, and the
|
|
18
|
+
# default identifier (:request_id) uses it, so each conversation already
|
|
19
|
+
# gets its own session. Scope by flow as well.
|
|
20
|
+
config.use_session_config(boundaries: [:flow])
|
|
22
21
|
end
|
|
23
22
|
|
|
24
23
|
# Run the customer support flow
|
|
@@ -29,16 +28,10 @@ end
|
|
|
29
28
|
# Example flow for handling customer conversations via Intercom
|
|
30
29
|
class CustomerSupportFlow < FlowChat::Flow
|
|
31
30
|
def handle_conversation
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
"Hello! I'm here to help you. How can I assist you today?"
|
|
35
|
-
else
|
|
36
|
-
"Thanks for your message! Let me help you with that."
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Get the user's inquiry
|
|
31
|
+
# Ask the opening question. The prompt is only shown on the first unanswered
|
|
32
|
+
# turn; once the user replies, screen(:inquiry) returns their stored answer.
|
|
40
33
|
inquiry = app.screen(:inquiry) do |prompt|
|
|
41
|
-
prompt.ask
|
|
34
|
+
prompt.ask "Hello! I'm here to help you. How can I assist you today?"
|
|
42
35
|
end
|
|
43
36
|
|
|
44
37
|
# Categorize the inquiry and provide appropriate response
|
|
@@ -61,11 +54,6 @@ class CustomerSupportFlow < FlowChat::Flow
|
|
|
61
54
|
|
|
62
55
|
private
|
|
63
56
|
|
|
64
|
-
def first_message?
|
|
65
|
-
# Check if this is the first message in the conversation
|
|
66
|
-
app.session.get("message_count").nil?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
57
|
def categorize_inquiry(inquiry)
|
|
70
58
|
# Simple keyword-based categorization
|
|
71
59
|
# In a real app, you might use AI/ML for better categorization
|
|
@@ -171,7 +159,7 @@ class CustomerSupportFlow < FlowChat::Flow
|
|
|
171
159
|
end
|
|
172
160
|
|
|
173
161
|
def escalate_to_human(reason, team_id: nil)
|
|
174
|
-
conversation_id = app.context["request.
|
|
162
|
+
conversation_id = app.context["request.id"]
|
|
175
163
|
|
|
176
164
|
# Business logic: Use Intercom manager for conversation control
|
|
177
165
|
if app.gateway == :intercom_api
|