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
|
@@ -1,171 +1,168 @@
|
|
|
1
|
-
# Gateway
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## All
|
|
6
|
-
|
|
7
|
-
| Variable | USSD Nalo | HTTP Simple | WhatsApp Cloud API | Telegram Bot API | Intercom API | Description |
|
|
8
|
-
|
|
9
|
-
| **Common
|
|
10
|
-
| `request.id` |
|
|
11
|
-
| `request.user_id` |
|
|
12
|
-
| `request.user_name` |
|
|
13
|
-
| `request.username` |
|
|
14
|
-
| `request.msisdn` |
|
|
15
|
-
| `request.email` |
|
|
16
|
-
| `request.message_id` |
|
|
17
|
-
| `request.timestamp` |
|
|
18
|
-
| `request.gateway` |
|
|
19
|
-
| `request.platform` |
|
|
20
|
-
| `request.body` |
|
|
21
|
-
| `request.input` |
|
|
22
|
-
| **
|
|
23
|
-
| `request.location` |
|
|
24
|
-
| `request.media` |
|
|
25
|
-
| `request.contact` |
|
|
26
|
-
|
|
|
27
|
-
| `whatsapp.business.
|
|
28
|
-
| `whatsapp.
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
| `
|
|
36
|
-
|
|
|
37
|
-
| `
|
|
38
|
-
|
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
# Gateway context variables
|
|
2
|
+
|
|
3
|
+
Every gateway parses its platform's webhook into a common set of context values. Flows and middleware read these instead of platform-specific request shapes, which is what lets one flow run everywhere. This document lists what each gateway sets.
|
|
4
|
+
|
|
5
|
+
## All context variables
|
|
6
|
+
|
|
7
|
+
| Variable | USSD Nalo | HTTP Simple | WhatsApp Cloud API | Messenger Send API | Instagram Send API | Telegram Bot API | Intercom API | Description |
|
|
8
|
+
|----------|-----------|-------------|-------------------|--------------------|--------------------|------------------|--------------|-------------|
|
|
9
|
+
| **Common** |
|
|
10
|
+
| `request.id` | Session id | From user_params | Phone number | PSID | IGSID | Chat id | Conversation id | Session or conversation identifier |
|
|
11
|
+
| `request.user_id` | = msisdn | From user_params | Phone number | PSID | IGSID | Telegram user id | Contact id | Stable per-user identifier |
|
|
12
|
+
| `request.user_name` | none | optional | optional | none | none | First and last name | optional | Sender display name |
|
|
13
|
+
| `request.username` | none | none | none | none | none | Telegram @username | none | Telegram username |
|
|
14
|
+
| `request.msisdn` | set | optional | set | nil | nil | none | optional | E.164 phone number |
|
|
15
|
+
| `request.email` | none | optional | none | none | none | none | optional | User email |
|
|
16
|
+
| `request.message_id` | UUID | UUID | WhatsApp id | `mid` | `mid` | Telegram msg id | optional | Message identifier |
|
|
17
|
+
| `request.timestamp` | Current | Current | Current | Current | Current | From message | Current | ISO8601 timestamp |
|
|
18
|
+
| `request.gateway` | `:nalo` | `:http_simple` | `:whatsapp_cloud_api` | `:messenger_send_api` | `:instagram_send_api` | `:telegram_bot_api` | `:intercom_api` | Gateway symbol |
|
|
19
|
+
| `request.platform` | `:ussd` | `:http` | `:whatsapp` | `:messenger` | `:instagram` | `:telegram` | `:intercom` | Platform symbol |
|
|
20
|
+
| `request.body` | set | set | set | set | set | set | set | Raw request body, string keys |
|
|
21
|
+
| `request.input` | Text | Text | Text (note 1) | Text (note 4) | Text (note 4) | Text (note 2) | Text or nil (note 3) | The turn's text |
|
|
22
|
+
| **Structured attachments** |
|
|
23
|
+
| `request.location` | none | none | set | set | set | set | none | Location payload |
|
|
24
|
+
| `request.media` | none | via `media_url` | set | set | set | set | set (may be several) | Media metadata |
|
|
25
|
+
| `request.contact` | none | none | none | none | none | set | none | Contact payload |
|
|
26
|
+
| **WhatsApp** |
|
|
27
|
+
| `whatsapp.business.phone_number` | | | E.164 business number | | | | | |
|
|
28
|
+
| `whatsapp.business.phone_number_id` | | | WhatsApp phone number id | | | | | |
|
|
29
|
+
| `whatsapp.client` | | | client instance | | | | | |
|
|
30
|
+
| **Messenger** |
|
|
31
|
+
| `messenger.account.id` | | | | Page id | | | | |
|
|
32
|
+
| `messenger.client` | | | | client instance | | | | |
|
|
33
|
+
| **Instagram** |
|
|
34
|
+
| `instagram.account.id` | | | | | Page id or Instagram account id, whichever the webhook named | | | |
|
|
35
|
+
| `instagram.client` | | | | | client instance | | | |
|
|
36
|
+
| **Telegram** |
|
|
37
|
+
| `telegram.client` | | | | | | client instance | | |
|
|
38
|
+
| `telegram.chat_type` | | | | | | private, group, supergroup, channel | | |
|
|
39
|
+
| `telegram.callback_query_id` | | | | | | on callbacks | | |
|
|
40
|
+
| `telegram.original_message_id` | | | | | | on callbacks | | |
|
|
41
|
+
| **HTTP** |
|
|
42
|
+
| `http.method` | | GET or POST | | | | | | |
|
|
43
|
+
| `http.path` | | Request path | | | | | | |
|
|
44
|
+
| `http.user_agent` | | User agent header | | | | | | |
|
|
45
|
+
| **Intercom** |
|
|
46
|
+
| `intercom.client` | | | | | | | client instance | |
|
|
47
|
+
| `intercom.topic` | | | | | | | Webhook event type | |
|
|
48
|
+
|
|
49
|
+
Notes on `request.input`:
|
|
50
|
+
|
|
51
|
+
1. WhatsApp: the message text, a media caption, or a button/list reply id. `""` for a structured turn (location, media, contact) that carries no text.
|
|
52
|
+
2. Telegram: the message text, callback data, or a media caption. `""` for a structured turn with no text.
|
|
53
|
+
3. Intercom: the message text or body, or `""`/`nil` for turns without text.
|
|
54
|
+
4. Messenger and Instagram: the message text, a media caption, a quick-reply payload, or a postback payload. `""` for a turn with no text.
|
|
55
|
+
|
|
56
|
+
`request.msisdn` is `nil` on both Messenger and Instagram: neither platform exposes a phone number, only a PSID or IGSID scoped to the app and the connected account. Use `request.user_id` (or `app.user_id`) as the durable per-user identifier; sessions key on it by default for both platforms.
|
|
57
|
+
|
|
58
|
+
5. Messenger and Instagram set `request.location`, not `request.media`, for a shared location: the gateway inspects the attachment's `type` and routes a `location` attachment to `request.location` with latitude, longitude, and name, the same as WhatsApp and Telegram. Every other attachment type still sets `request.media`.
|
|
59
|
+
|
|
60
|
+
`context.input` is always plain text. There are no `"$media$"`/`"$location$"`/`"$contact$"` sentinel values: a structured turn with no text sets `input` to `""` and carries its payload on `request.media`, `request.location`, or `request.contact`. In flows, read `app.input` (a `FlowChat::Input`) or its accessors, described below.
|
|
61
|
+
|
|
62
|
+
## Reading the turn in a flow
|
|
63
|
+
|
|
64
|
+
Prefer the accessors on `app` over reading context keys directly. Every turn is a `FlowChat::Input` value object with two independent axes: text and an optional attachment. Text and media can arrive together (a captioned photo); location and contact arrive on their own.
|
|
44
65
|
|
|
45
66
|
```ruby
|
|
46
67
|
class MyFlow < FlowChat::Flow
|
|
47
68
|
def start
|
|
48
|
-
#
|
|
49
|
-
user_id
|
|
50
|
-
user_name = app.context["request.user_name"] # Available from WhatsApp, Intercom, HTTP (optional)
|
|
51
|
-
msisdn = app.context["request.msisdn"] # Available from USSD, WhatsApp, HTTP (optional)
|
|
52
|
-
email = app.context["request.email"] # Available from HTTP (optional)
|
|
53
|
-
platform = app.context["request.platform"]
|
|
54
|
-
input = app.context["request.input"]
|
|
55
|
-
|
|
56
|
-
# Or use convenience methods
|
|
57
|
-
user_id = app.user_id
|
|
69
|
+
# Identity and platform, available on every platform.
|
|
70
|
+
user_id = app.user_id
|
|
58
71
|
platform = app.platform
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
msisdn = app.msisdn
|
|
73
|
+
|
|
74
|
+
# The turn's text. Always a string, "" when the turn carried no text.
|
|
75
|
+
message = app.text
|
|
76
|
+
|
|
77
|
+
# Branch on the attachment kind, not on a magic input value.
|
|
78
|
+
case app.attachment_type
|
|
79
|
+
when :media
|
|
80
|
+
app.media.each do |item| # always a list; iterate so you never drop extra attachments
|
|
81
|
+
item.type # canonical: :image, :video, :audio, :document, :sticker
|
|
82
|
+
item.raw_type # platform-native: :photo or :voice on Telegram
|
|
83
|
+
item.mime_type
|
|
84
|
+
item.filename
|
|
85
|
+
link = item.url # a fetchable URL, or nil
|
|
86
|
+
bytes = item.download # the raw file bytes, or nil
|
|
71
87
|
end
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# Handle special input types
|
|
79
|
-
case input
|
|
80
|
-
when "$location$"
|
|
81
|
-
location = app.context["request.location"]
|
|
82
|
-
lat = location["latitude"]
|
|
83
|
-
lng = location["longitude"]
|
|
84
|
-
when "$media$"
|
|
85
|
-
media = app.context["request.media"]
|
|
86
|
-
file_id = media[:file_id]
|
|
87
|
-
media_type = media[:type] # :photo, :document, :voice
|
|
88
|
-
when "$contact$"
|
|
89
|
-
contact = app.context["request.contact"]
|
|
90
|
-
phone = contact[:phone_number]
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
when :intercom
|
|
94
|
-
topic = app.context["intercom.topic"]
|
|
95
|
-
client = app.context["intercom.client"]
|
|
96
|
-
|
|
97
|
-
# Handle events without messages
|
|
98
|
-
if input.nil?
|
|
99
|
-
# Event without user message (e.g., admin-initiated)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
when :http
|
|
103
|
-
method = app.context["http.method"]
|
|
104
|
-
user_agent = app.context["http.user_agent"]
|
|
105
|
-
|
|
106
|
-
when :ussd
|
|
107
|
-
msisdn = app.context["request.msisdn"]
|
|
88
|
+
when :location
|
|
89
|
+
lat = app.location[:latitude]
|
|
90
|
+
lng = app.location[:longitude]
|
|
91
|
+
when :contact
|
|
92
|
+
name = app.contact[:name]
|
|
108
93
|
end
|
|
109
94
|
end
|
|
110
95
|
end
|
|
111
96
|
```
|
|
112
97
|
|
|
113
|
-
|
|
98
|
+
Accessors, all shortcuts to the `app.input` value object:
|
|
99
|
+
|
|
100
|
+
- `app.text`: the turn's text, or the caption sent with an attachment. Always a string, `""` when there is no text.
|
|
101
|
+
- `app.attachment_type`: `:media`, `:location`, `:contact`, or `nil`, the discriminator to branch on.
|
|
102
|
+
- `app.attachment`: the payload of `attachment_type` (the media list, the location hash, the contact hash, or `nil`).
|
|
103
|
+
- `app.media`: always an `Array<FlowChat::Media>` (empty when none), a list even on single-media platforms, so you iterate uniformly.
|
|
104
|
+
- `app.location`: the location hash, or `nil`.
|
|
105
|
+
- `app.contact`: the shared contact card hash, or `nil`.
|
|
106
|
+
- `app.contact_name`: the sender's display name (distinct from a shared contact card).
|
|
114
107
|
|
|
115
|
-
|
|
108
|
+
## Media
|
|
116
109
|
|
|
117
|
-
|
|
110
|
+
WhatsApp, Messenger, Instagram, Telegram, Intercom, and HTTP set `request.media` for inbound media (USSD is text-only and never sets it). WhatsApp, Messenger, Instagram, and Telegram carry a single item; Intercom may carry several, one per attachment. HTTP callers submit inbound media through the `media_url` request param, with optional `media_type` and `mime_type`.
|
|
118
111
|
|
|
119
|
-
|
|
112
|
+
A `FlowChat::Media` item's `type` is a normalized value; `raw_type` is the platform-native value. The normalization maps `:photo` to `:image` and `:voice` to `:audio`, so `type` is one of `:image`, `:video`, `:audio`, `:document`, `:sticker`.
|
|
120
113
|
|
|
121
|
-
|
|
114
|
+
| Media type | WhatsApp | Messenger / Instagram | Telegram | Fields |
|
|
115
|
+
|------------|----------|------------------------|----------|--------|
|
|
116
|
+
| `:image` (Telegram `:photo`) | yes | yes | yes | id or file_id, mime_type, width, height |
|
|
117
|
+
| `:video` | yes | yes | yes | id or file_id, mime_type, duration, width, height |
|
|
118
|
+
| `:audio` | yes | yes | yes | id or file_id, mime_type, duration, title, performer |
|
|
119
|
+
| `:voice` | no | no | yes | file_id, mime_type, duration |
|
|
120
|
+
| `:document` (Messenger/Instagram `file`) | yes | yes | yes | id or file_id, mime_type, filename |
|
|
121
|
+
| `:sticker` | yes | not confirmed | yes | id or file_id, emoji, set_name, is_animated |
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
Messenger and Instagram attachments carry only `type` and `url`; there is no separate mime_type, filename, width, height, or duration, because the gateway does not call a lookup API the way WhatsApp does for a media id. `item.url` resolves a fetchable URL per platform (WhatsApp `get_media_url`, Telegram `getFile`, Messenger/Instagram/Intercom/HTTP use the direct URL already on the attachment). `item.download` returns the raw bytes.
|
|
124
124
|
|
|
125
|
-
|
|
126
|
-
|------------|----------|----------|-------------------|
|
|
127
|
-
| `:image` / `:photo` | ✓ `:image` | ✓ `:photo` | id/file_id, mime_type, width, height |
|
|
128
|
-
| `:video` | ✓ | ✓ | id/file_id, mime_type, duration, width, height |
|
|
129
|
-
| `:audio` | ✓ | ✓ | id/file_id, mime_type, duration, title, performer |
|
|
130
|
-
| `:voice` | ✗ | ✓ | file_id, mime_type, duration |
|
|
131
|
-
| `:document` | ✓ | ✓ | id/file_id, mime_type, filename |
|
|
132
|
-
| `:sticker` | ✓ | ✓ | id/file_id, emoji, set_name, is_animated |
|
|
125
|
+
Meta routes a shared location to `request.location`, not `request.media`, the same as WhatsApp and Telegram - a location share is not a `FlowChat::Media` item on any of these platforms.
|
|
133
126
|
|
|
134
|
-
###
|
|
127
|
+
### Inspecting attachments in validate and transform
|
|
135
128
|
|
|
136
|
-
FlowChat
|
|
129
|
+
The `FlowChat::Input` object behaves like its text for string operations, so validators and transforms read naturally, and it also exposes the attachment:
|
|
137
130
|
|
|
138
131
|
```ruby
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
132
|
+
app.screen(:photo) do |prompt|
|
|
133
|
+
prompt.ask "Send your ID photo",
|
|
134
|
+
validate: ->(input) { "Please attach a photo" unless input.media.any? }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
app.screen(:name) do |prompt|
|
|
138
|
+
prompt.ask "Your name?", transform: ->(input) { input.strip.titleize }
|
|
139
|
+
end
|
|
143
140
|
```
|
|
144
141
|
|
|
145
|
-
|
|
142
|
+
A turn counts as answered (`input.submitted?`) when it has text or an attachment, so a caption-less photo still satisfies a screen.
|
|
143
|
+
|
|
144
|
+
### What a screen should return
|
|
145
|
+
|
|
146
|
+
`prompt.ask` and `prompt.select` return a string (the text, or your `transform`'s result), and that string is what gets stored as the screen's answer. Return those from screen blocks.
|
|
147
|
+
|
|
148
|
+
`prompt.user_input` is the raw `FlowChat::Input` object, useful inside a `validate` or `transform` when you want the attachment, but avoid returning it as a screen's value:
|
|
146
149
|
|
|
147
150
|
```ruby
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
file_id = media[:file_id] || media[:id]
|
|
155
|
-
when :video
|
|
156
|
-
duration = media[:duration]
|
|
157
|
-
when :document
|
|
158
|
-
filename = media[:file_name] || media[:filename]
|
|
159
|
-
when :sticker
|
|
160
|
-
emoji = media[:emoji]
|
|
161
|
-
end
|
|
151
|
+
# Good: persists a string.
|
|
152
|
+
name = app.screen(:name) { |prompt| prompt.ask "Your name?" }
|
|
153
|
+
|
|
154
|
+
# Avoid: persists the whole Input object into the session store.
|
|
155
|
+
raw = app.screen(:raw) { |prompt| prompt.user_input }
|
|
156
|
+
```
|
|
162
157
|
|
|
163
|
-
|
|
164
|
-
location = app.context["request.location"]
|
|
165
|
-
lat, lng = location["latitude"], location["longitude"]
|
|
158
|
+
Whatever a screen returns is stored in the session and serialized by the store (`CacheSessionStore` uses `Marshal`). A `FlowChat::Media` serializes without its live platform client, so a media object deserialized from the session has no client and its `url` and `download` return `nil` rather than raising. Fetch media during the turn it arrives, while the client is present; do not rely on downloading it from a stored answer on a later turn.
|
|
166
159
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
160
|
+
### Lower-level access
|
|
161
|
+
|
|
162
|
+
The raw request hashes remain available if you need them, but prefer the accessors above:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
app.context["request.media"] # raw Hash, or Array for Intercom
|
|
166
|
+
app.context["request.location"]
|
|
167
|
+
app.context["request.contact"]
|
|
171
168
|
```
|