flow_chat 0.8.2 → 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/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/pages.yml +43 -0
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +48 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +189 -133
- data/Rakefile +17 -2
- data/SECURITY.md +42 -349
- data/docs/architecture.md +83 -0
- data/docs/async-background-processing.md +64 -0
- data/docs/configuration.md +110 -287
- data/docs/factory-pattern.md +58 -0
- data/docs/gateway-context-variables.md +168 -0
- data/docs/gateway-development.md +159 -0
- data/docs/getting-started.md +90 -0
- data/docs/instrumentation.md +95 -175
- data/docs/platforms/instagram.md +278 -0
- data/docs/platforms/messenger.md +205 -0
- data/docs/platforms/telegram.md +109 -0
- data/docs/platforms/ussd.md +78 -0
- data/docs/platforms/whatsapp.md +147 -0
- 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 +33 -426
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +22 -20
- data/examples/intercom_configuration_example.rb +113 -0
- data/examples/intercom_controller.rb +182 -0
- data/examples/multi_tenant_whatsapp_controller.rb +63 -168
- data/examples/simulator_controller.rb +0 -1
- data/examples/ussd_controller.rb +88 -160
- data/examples/whatsapp_controller.rb +18 -17
- data/examples/whatsapp_media_examples.rb +27 -79
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/app.rb +211 -0
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/choice_titles.rb +95 -0
- data/lib/flow_chat/config.rb +126 -23
- data/lib/flow_chat/delivery_error.rb +9 -0
- 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/configuration_error.rb +9 -0
- data/lib/flow_chat/http/gateway/simple.rb +104 -36
- data/lib/flow_chat/http/middleware/choice_mapper.rb +94 -0
- data/lib/flow_chat/http/renderer.rb +3 -3
- 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/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +182 -0
- data/lib/flow_chat/intercom/client.rb +161 -0
- data/lib/flow_chat/intercom/configuration.rb +102 -0
- data/lib/flow_chat/intercom/configuration_error.rb +9 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +420 -0
- data/lib/flow_chat/intercom/middleware/choice_mapper.rb +101 -0
- data/lib/flow_chat/intercom/renderer.rb +123 -0
- 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/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/prompt.rb +13 -16
- data/lib/flow_chat/renderers/markdown_support.rb +167 -0
- data/lib/flow_chat/security.rb +76 -0
- data/lib/flow_chat/session/middleware.rb +36 -11
- 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 +283 -0
- data/lib/flow_chat/telegram/configuration.rb +78 -0
- data/lib/flow_chat/telegram/configuration_error.rb +9 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +318 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +96 -0
- data/lib/flow_chat/telegram/renderer.rb +133 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/text_truncator.rb +75 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/choice_mapper.rb +10 -0
- 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 +158 -20
- data/lib/flow_chat/whatsapp/configuration.rb +13 -52
- data/lib/flow_chat/whatsapp/configuration_error.rb +9 -0
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +335 -248
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +234 -0
- data/lib/flow_chat/whatsapp/renderer.rb +259 -64
- data/lib/flow_chat.rb +1 -1
- data/lib/tasks/release.rake +165 -0
- 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 +132 -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_app.rb +0 -86
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a748d7d7a637ca0dc2e0f7c5d8dde213df8fd46eec9117417f141ed6d8aff91d
|
|
4
|
+
data.tar.gz: ae52d99625691a839cda0e6884ab065bfc5851aa427ceafc4c19ded649c13da7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05e3a2dc63c3c678f0da32f180121caece864af244181f1deb1b668118244e4c342e5656aac7533b79eb3d6f0b3f9f87c62c0ca9faa14243cf9c1ab04c06bea6
|
|
7
|
+
data.tar.gz: 804ca337d0842bd077f2f2fbde1c864d993ba22d87915e92d869103b8d9adb8ff898cd598dc476bff7625772151be8f49e3853ec93d010cb646e19d9b54e8a06
|
data/.cliff.toml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# git-cliff configuration file
|
|
2
|
+
# https://git-cliff.org/docs/configuration
|
|
3
|
+
|
|
4
|
+
[changelog]
|
|
5
|
+
# changelog header
|
|
6
|
+
header = """
|
|
7
|
+
# Changelog\n
|
|
8
|
+
All notable changes to this project will be documented in this file.\n
|
|
9
|
+
"""
|
|
10
|
+
# template for the changelog body
|
|
11
|
+
body = """
|
|
12
|
+
{% if version %}\
|
|
13
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
14
|
+
{% else %}\
|
|
15
|
+
## [Unreleased]
|
|
16
|
+
{% endif %}\
|
|
17
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
|
18
|
+
### {{ group | upper_first }}
|
|
19
|
+
{% for commit in commits %}
|
|
20
|
+
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
|
21
|
+
{% endfor %}
|
|
22
|
+
{% endfor %}\n
|
|
23
|
+
"""
|
|
24
|
+
# remove the leading and trailing whitespace from the template
|
|
25
|
+
trim = true
|
|
26
|
+
# changelog footer
|
|
27
|
+
footer = """
|
|
28
|
+
<!-- generated by git-cliff -->
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
[git]
|
|
32
|
+
# parse the commits based on https://www.conventionalcommits.org
|
|
33
|
+
conventional_commits = true
|
|
34
|
+
# filter out the commits that are not conventional
|
|
35
|
+
filter_unconventional = true
|
|
36
|
+
# process each line of a commit as an individual commit
|
|
37
|
+
split_commits = false
|
|
38
|
+
# regex for preprocessing the commit messages
|
|
39
|
+
commit_preprocessors = [
|
|
40
|
+
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/radioactive-labs/flow_chat/issues/${2}))" },
|
|
41
|
+
]
|
|
42
|
+
# regex for parsing and grouping commits
|
|
43
|
+
commit_parsers = [
|
|
44
|
+
{ message = "^feat", group = "Features" },
|
|
45
|
+
{ message = "^fix", group = "Bug Fixes" },
|
|
46
|
+
{ message = "^doc", group = "Documentation" },
|
|
47
|
+
{ message = "^perf", group = "Performance" },
|
|
48
|
+
{ message = "^refactor", group = "Refactoring" },
|
|
49
|
+
{ message = "^style", group = "Styling" },
|
|
50
|
+
{ message = "^test", group = "Testing" },
|
|
51
|
+
{ message = "^chore\\(release\\): prepare for", skip = true },
|
|
52
|
+
{ message = "^chore", group = "Miscellaneous Tasks" },
|
|
53
|
+
{ body = ".*security", group = "Security" },
|
|
54
|
+
]
|
|
55
|
+
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
|
56
|
+
protect_breaking_commits = false
|
|
57
|
+
# filter out the commits that are not matched by commit parsers
|
|
58
|
+
filter_commits = false
|
|
59
|
+
# glob pattern for matching git tags
|
|
60
|
+
tag_pattern = "v[0-9]*"
|
|
61
|
+
# regex for skipping tags
|
|
62
|
+
skip_tags = ""
|
|
63
|
+
# regex for ignoring tags
|
|
64
|
+
ignore_tags = ""
|
|
65
|
+
# sort the tags topologically
|
|
66
|
+
topo_order = false
|
|
67
|
+
# sort the commits inside sections by oldest/newest order
|
|
68
|
+
sort_commits = "oldest"
|
|
69
|
+
|
|
70
|
+
[bump]
|
|
71
|
+
# Pre-1.0 semver: a feature bumps the minor, a breaking change also bumps the
|
|
72
|
+
# minor (not the major) while we're on 0.x. Fixes bump the patch.
|
|
73
|
+
features_always_bump_minor = true
|
|
74
|
+
breaking_always_bump_major = false
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Deploy landing page to Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
# Runs on pushes to the default branch that touch the site.
|
|
5
|
+
push:
|
|
6
|
+
branches: [master]
|
|
7
|
+
paths:
|
|
8
|
+
- "site/**"
|
|
9
|
+
- ".github/workflows/pages.yml"
|
|
10
|
+
|
|
11
|
+
# Allows running this workflow manually from the Actions tab.
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
# Grant GITHUB_TOKEN the permissions required to deploy to GitHub Pages.
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
pages: write
|
|
18
|
+
id-token: write
|
|
19
|
+
|
|
20
|
+
# Allow one concurrent deployment; don't cancel an in-progress production deploy.
|
|
21
|
+
concurrency:
|
|
22
|
+
group: pages
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
# No build step: the page is a single static file. Upload site/ and deploy it.
|
|
26
|
+
jobs:
|
|
27
|
+
deploy:
|
|
28
|
+
environment:
|
|
29
|
+
name: github-pages
|
|
30
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- name: Checkout
|
|
34
|
+
uses: actions/checkout@v4
|
|
35
|
+
- name: Setup Pages
|
|
36
|
+
uses: actions/configure-pages@v4
|
|
37
|
+
- name: Upload artifact
|
|
38
|
+
uses: actions/upload-pages-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
path: site
|
|
41
|
+
- name: Deploy to GitHub Pages
|
|
42
|
+
id: deployment
|
|
43
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing to RubyGems happens from a laptop (rake release:publish). This
|
|
4
|
+
# workflow only cuts the GitHub Release from the pushed tag — it never pushes to
|
|
5
|
+
# a registry, so it can't race the local publish. workflow_dispatch is kept as a
|
|
6
|
+
# manual escape hatch.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags:
|
|
10
|
+
- 'v*'
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
release:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Set up Ruby
|
|
26
|
+
uses: ruby/setup-ruby@v1
|
|
27
|
+
with:
|
|
28
|
+
ruby-version: '3.2'
|
|
29
|
+
bundler-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Extract version from tag
|
|
32
|
+
id: version
|
|
33
|
+
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
34
|
+
|
|
35
|
+
# Build the gem only to attach it to the GitHub Release — it is NOT pushed
|
|
36
|
+
# to RubyGems here (the laptop already did that via rake release:publish).
|
|
37
|
+
- name: Build gem
|
|
38
|
+
run: gem build flow_chat.gemspec
|
|
39
|
+
|
|
40
|
+
- name: Install git-cliff
|
|
41
|
+
uses: taiki-e/install-action@v2
|
|
42
|
+
with:
|
|
43
|
+
tool: git-cliff
|
|
44
|
+
|
|
45
|
+
- name: Generate release notes
|
|
46
|
+
run: |
|
|
47
|
+
git-cliff --config .cliff.toml --tag ${{ github.ref_name }} --strip all > RELEASE_NOTES.md
|
|
48
|
+
|
|
49
|
+
- name: Create GitHub Release
|
|
50
|
+
uses: softprops/action-gh-release@v1
|
|
51
|
+
with:
|
|
52
|
+
body_path: RELEASE_NOTES.md
|
|
53
|
+
files: |
|
|
54
|
+
flow_chat-${{ steps.version.outputs.VERSION }}.gem
|
|
55
|
+
env:
|
|
56
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.10.0] - 2026-08-17
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Correct runtime-breaking session and API misuse
|
|
10
|
+
- Use the full SHA256 for hashed identifiers
|
|
11
|
+
- Use a 128-bit (32-char) hashed identifier
|
|
12
|
+
- Give api.error a contract other than its prose ([#5](https://github.com/radioactive-labs/flow_chat/issues/5))
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
- [**breaking**] Revamp README and all docs, add GitHub Pages landing page ([#3](https://github.com/radioactive-labs/flow_chat/issues/3))
|
|
17
|
+
- Explain how session boundaries build the session id
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- Map choices on Intercom and HTTP ([#4](https://github.com/radioactive-labs/flow_chat/issues/4))
|
|
22
|
+
- Let a flow declare that it consumed the turn ([#6](https://github.com/radioactive-labs/flow_chat/issues/6))
|
|
23
|
+
- Report a reply the platform would not take ([#7](https://github.com/radioactive-labs/flow_chat/issues/7))
|
|
24
|
+
- Dispatch webhooks on their field, and handle coexistence ([#8](https://github.com/radioactive-labs/flow_chat/issues/8))
|
|
25
|
+
|
|
26
|
+
### Refactoring
|
|
27
|
+
|
|
28
|
+
- Fix outdated APIs, dedupe, and harden
|
|
29
|
+
- Drop dead first_message? branch in Intercom flow
|
|
30
|
+
|
|
31
|
+
## [0.9.0] - 2026-07-09
|
|
32
|
+
|
|
33
|
+
### Features
|
|
34
|
+
|
|
35
|
+
- Multi-platform gateways, composition architecture, and async processing ([#1](https://github.com/radioactive-labs/flow_chat/issues/1))
|
|
36
|
+
|
|
37
|
+
### Miscellaneous Tasks
|
|
38
|
+
|
|
39
|
+
- Add git-cliff changelog and release rake tasks
|
|
40
|
+
- Add security policy and require RubyGems MFA
|
|
41
|
+
|
|
42
|
+
### Styling
|
|
43
|
+
|
|
44
|
+
- Adopt StandardRB and format the codebase
|
|
45
|
+
|
|
46
|
+
## [0.1.0] - 2024-04-04
|
|
47
|
+
|
|
48
|
+
<!-- generated by git-cliff -->
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Development Commands
|
|
6
|
+
|
|
7
|
+
### Testing
|
|
8
|
+
- `rake test` or `bundle exec rake test` - Run all tests
|
|
9
|
+
- `ruby -Itest test/unit/specific_test.rb` - Run a single test file
|
|
10
|
+
- `ruby -Itest test/unit/specific_test.rb -n test_method_name` - Run a specific test method
|
|
11
|
+
|
|
12
|
+
### Gem Development
|
|
13
|
+
- `bundle install` - Install dependencies
|
|
14
|
+
- `rake build` - Build the gem
|
|
15
|
+
- `rake release` - Build and release the gem (requires proper credentials)
|
|
16
|
+
|
|
17
|
+
### Rails Integration Testing
|
|
18
|
+
- Use `rails runner` instead of `rails console` for scripting
|
|
19
|
+
- For production logs, use Rails.logger with block syntax: `Rails.logger.warn { "message" }`
|
|
20
|
+
|
|
21
|
+
## Architecture Overview
|
|
22
|
+
|
|
23
|
+
FlowChat is a Rails framework for building conversational interfaces across multiple platforms (USSD, WhatsApp, Telegram, HTTP) using a **composition-based architecture** with **pluggable gateways**.
|
|
24
|
+
|
|
25
|
+
### Core Components
|
|
26
|
+
|
|
27
|
+
#### Processor (`lib/flow_chat/processor.rb`)
|
|
28
|
+
- Central orchestrator that builds and executes the middleware stack
|
|
29
|
+
- Configures gateways, session stores, and middleware
|
|
30
|
+
- Entry point: `FlowChat::Processor.new(controller) do |config|`
|
|
31
|
+
|
|
32
|
+
#### Gateway
|
|
33
|
+
- Platform-specific request/response handling
|
|
34
|
+
- Built-in gateways: `FlowChat::Ussd::Gateway::Nalo`, `FlowChat::Whatsapp::Gateway::CloudApi`, `FlowChat::Telegram::Gateway::BotApi`, `FlowChat::Http::Gateway::Simple`
|
|
35
|
+
- Custom gateways implement: `initialize(app, *args)` and `call(context)`
|
|
36
|
+
|
|
37
|
+
#### App (`lib/flow_chat/app.rb`)
|
|
38
|
+
- Unified interface for flows to interact with users
|
|
39
|
+
- Key method: `screen(key) { |prompt| ... }` for conversation logic
|
|
40
|
+
- Platform-agnostic accessors: `msisdn`, `user_id`, `platform`, etc.
|
|
41
|
+
|
|
42
|
+
#### Flow (`lib/flow_chat/flow.rb`)
|
|
43
|
+
- Simple base class containing conversation logic
|
|
44
|
+
- Initialize with `app` instance, implement flow methods
|
|
45
|
+
|
|
46
|
+
#### Session (`lib/flow_chat/session/`)
|
|
47
|
+
- Configurable session boundaries: `:flow`, `:platform`, `:gateway`, `:url`
|
|
48
|
+
- Session stores: `CacheSessionStore`
|
|
49
|
+
- Session IDs generated based on boundaries and identifiers
|
|
50
|
+
|
|
51
|
+
#### Factory Pattern (`lib/flow_chat/factory.rb`)
|
|
52
|
+
- Centralized processor configuration system
|
|
53
|
+
- Register configurations once, use everywhere
|
|
54
|
+
- Methods: `register(name, &block)`, `execute(name, controller:)`
|
|
55
|
+
- Eliminates duplication between webhook and background contexts
|
|
56
|
+
- Works seamlessly with `GenericAsyncJob` for async processing
|
|
57
|
+
|
|
58
|
+
#### Async Background Processing (`lib/flow_chat/async_job.rb`, `gateway_async_support.rb`, `generic_async_job.rb`)
|
|
59
|
+
- Decouple flow processing from webhook request-response cycles
|
|
60
|
+
- Base class: `FlowChat::AsyncJob` for custom background jobs
|
|
61
|
+
- `GenericAsyncJob`: Built-in job that uses Factory pattern (no custom job needed)
|
|
62
|
+
- `BackgroundController` mimics controller interface in background context
|
|
63
|
+
- `GatewayAsyncSupport` concern for gateways to detect and enqueue async jobs
|
|
64
|
+
- Automatic detection: async enqueue, background execute, or inline processing
|
|
65
|
+
- Supported gateways: WhatsApp Cloud API, Intercom API, HTTP Simple (not USSD)
|
|
66
|
+
|
|
67
|
+
### Middleware Stack Architecture
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Gateway -> Session::Middleware -> Custom Middleware -> Executor -> Flow
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **Gateway**: Parses platform-specific requests, renders responses
|
|
74
|
+
- **Session::Middleware**: Manages session boundaries and storage
|
|
75
|
+
- **Custom Middleware**: Business logic, authentication, logging
|
|
76
|
+
- **Executor**: Instantiates flows and handles interrupts
|
|
77
|
+
- **Flow**: Business logic using `app.screen()` for conversation
|
|
78
|
+
|
|
79
|
+
### Key Patterns
|
|
80
|
+
|
|
81
|
+
#### Screen-Based Navigation
|
|
82
|
+
```ruby
|
|
83
|
+
def registration_flow
|
|
84
|
+
email = app.screen(:email) { |p| p.ask "Enter email:", validate: email_validator }
|
|
85
|
+
name = app.screen(:name) { |p| p.ask "Enter name:", transform: ->(input) { input.titleize } }
|
|
86
|
+
app.say "Welcome #{name}!"
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Platform Configuration
|
|
91
|
+
```ruby
|
|
92
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
93
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
94
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
95
|
+
config.use_session_config(boundaries: [:flow], identifier: :msisdn)
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Multi-Platform Support
|
|
100
|
+
Same flow code works across USSD, WhatsApp, Telegram, and HTTP by using platform-agnostic `app.screen()` calls.
|
|
101
|
+
|
|
102
|
+
#### Factory Pattern with Async
|
|
103
|
+
The recommended approach for async processing using centralized configuration:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Register factory once in initializer
|
|
107
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
108
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
109
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
110
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
111
|
+
config.use_async(factory: :whatsapp) # Self-referencing for async
|
|
112
|
+
end
|
|
113
|
+
processor.run(WhatsAppFlow, :start)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Use in webhook controller - one line!
|
|
117
|
+
FlowChat::Factory.execute(:whatsapp, controller: self)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**How it works:**
|
|
121
|
+
1. Webhook calls `Factory.execute(:whatsapp)`
|
|
122
|
+
2. Factory builds processor with `use_async(factory: :whatsapp)`
|
|
123
|
+
3. Gateway enqueues `GenericAsyncJob` with `factory: :whatsapp` param
|
|
124
|
+
4. Background job executes `Factory.execute(:whatsapp)` again
|
|
125
|
+
5. Gateway detects background context and processes inline
|
|
126
|
+
|
|
127
|
+
**Benefits:**
|
|
128
|
+
- No custom job class needed (`GenericAsyncJob` handles it automatically)
|
|
129
|
+
- Configuration defined once, works in both webhook and background contexts
|
|
130
|
+
- Webhook returns immediately (< 100ms), flow processes in background
|
|
131
|
+
- Automatic prevention of double-enqueueing
|
|
132
|
+
|
|
133
|
+
See [docs/factory-pattern.md](docs/factory-pattern.md) and [docs/async-background-processing.md](docs/async-background-processing.md) for details.
|
|
134
|
+
|
|
135
|
+
#### Custom Async Jobs
|
|
136
|
+
For advanced cases, create custom job classes with job params:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
class MyFlowJob < FlowChat::AsyncJob
|
|
140
|
+
def execute(controller, **job_params)
|
|
141
|
+
deployment_id = job_params[:deployment_id]
|
|
142
|
+
# ... custom logic with job params
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Use with job params
|
|
147
|
+
config.use_async(MyFlowJob, deployment_id: 123)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## File Structure
|
|
151
|
+
|
|
152
|
+
### Core Library (`lib/flow_chat/`)
|
|
153
|
+
- `processor.rb` - Main orchestrator, middleware stack builder
|
|
154
|
+
- `app.rb` - Unified conversation interface
|
|
155
|
+
- `flow.rb` - Base flow class
|
|
156
|
+
- `executor.rb` - Flow execution and interrupt handling
|
|
157
|
+
- `context.rb` - Request context management
|
|
158
|
+
- `config.rb` - Global configuration
|
|
159
|
+
- `factory.rb` - Centralized processor configuration registry
|
|
160
|
+
- `async_job.rb` - Background processing base class and controllers
|
|
161
|
+
- `generic_async_job.rb` - Factory-based async job (no custom class needed)
|
|
162
|
+
- `gateway_async_support.rb` - Async detection and enqueueing concern for gateways
|
|
163
|
+
|
|
164
|
+
### Platform Gateways
|
|
165
|
+
- `ussd/gateway/nalo.rb` - USSD platform integration (async not supported)
|
|
166
|
+
- `whatsapp/gateway/cloud_api.rb` - WhatsApp Business API integration (async supported)
|
|
167
|
+
- `telegram/gateway/bot_api.rb` - Telegram Bot API integration (async supported)
|
|
168
|
+
- `http/gateway/simple.rb` - HTTP/JSON API integration (async supported)
|
|
169
|
+
- `intercom/gateway/intercom_api.rb` - Intercom customer support integration (async supported)
|
|
170
|
+
- All gateways include `GatewayAsyncSupport` concern for unified async handling
|
|
171
|
+
|
|
172
|
+
### Session Management (`session/`)
|
|
173
|
+
- `middleware.rb` - Session boundary and ID generation
|
|
174
|
+
- `rails_session_store.rb` - Rails session integration
|
|
175
|
+
- `cache_session_store.rb` - Rails cache integration
|
|
176
|
+
|
|
177
|
+
### Platform-Specific (`ussd/`, `whatsapp/`, `telegram/`, `http/`, `intercom/`)
|
|
178
|
+
- `renderer.rb` - Platform-specific response formatting
|
|
179
|
+
- `middleware/` - Platform-specific processing logic
|
|
180
|
+
- `intercom/client.rb` - Intercom REST API integration
|
|
181
|
+
- `intercom/configuration.rb` - Intercom credentials and settings
|
|
182
|
+
- `intercom/conversation_manager.rb` - Generic conversation management utilities
|
|
183
|
+
|
|
184
|
+
### Testing (`test/`)
|
|
185
|
+
- `test_helper.rb` - Test setup with mock Rails environment
|
|
186
|
+
- `unit/` - Unit tests for individual components
|
|
187
|
+
- `integration/` - Integration tests for full flow scenarios
|
|
188
|
+
- `e2e/` - End-to-end tests for platform-specific features
|
|
189
|
+
|
|
190
|
+
## Testing Approach
|
|
191
|
+
|
|
192
|
+
- Uses Minitest with custom test helpers
|
|
193
|
+
- Mock Rails environment for testing without full Rails app
|
|
194
|
+
- Test flows using `mock_controller` and session store mocks
|
|
195
|
+
- Platform-specific tests verify gateway behavior
|
|
196
|
+
- Integration tests verify full request/response cycles
|
|
197
|
+
|
|
198
|
+
## Configuration Patterns
|
|
199
|
+
|
|
200
|
+
### Global Configuration
|
|
201
|
+
```ruby
|
|
202
|
+
# Logger injection into middleware stack (defaults to true in Rails development)
|
|
203
|
+
FlowChat::Config.inject_middleware_logger = true
|
|
204
|
+
|
|
205
|
+
# Other global configs
|
|
206
|
+
FlowChat::Config.logger = Rails.logger
|
|
207
|
+
FlowChat::Config.combine_validation_error_with_message = true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Session Configuration
|
|
211
|
+
```ruby
|
|
212
|
+
config.use_session_config(
|
|
213
|
+
boundaries: [:flow, :platform], # Session isolation
|
|
214
|
+
identifier: :msisdn, # Session key type
|
|
215
|
+
hash_identifiers: true # Privacy protection
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Multi-Tenancy Support
|
|
220
|
+
```ruby
|
|
221
|
+
config.use_url_isolation # tenant1.app.com vs tenant2.app.com
|
|
222
|
+
config.use_cross_platform_sessions # Share sessions between USSD/WhatsApp
|
|
223
|
+
config.use_durable_sessions # Use user_id instead of request_id
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Intercom Integration
|
|
227
|
+
|
|
228
|
+
FlowChat provides comprehensive Intercom integration for customer support workflows with proper webhook validation and API client functionality.
|
|
229
|
+
|
|
230
|
+
#### Configuration
|
|
231
|
+
```ruby
|
|
232
|
+
# Rails credentials (config/credentials.yml.enc)
|
|
233
|
+
intercom:
|
|
234
|
+
access_token: "your_intercom_access_token"
|
|
235
|
+
client_secret: "your_intercom_client_secret"
|
|
236
|
+
skip_signature_validation: false # Optional: disable webhook validation for testing
|
|
237
|
+
|
|
238
|
+
# Or environment variables
|
|
239
|
+
INTERCOM_ACCESS_TOKEN=your_intercom_access_token
|
|
240
|
+
INTERCOM_CLIENT_SECRET=your_intercom_client_secret
|
|
241
|
+
INTERCOM_SKIP_SIGNATURE_VALIDATION=false
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Gateway Setup
|
|
245
|
+
```ruby
|
|
246
|
+
# Basic Intercom gateway setup (default webhook topics)
|
|
247
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi
|
|
248
|
+
config.use_session_config(boundaries: [:conversation], identifier: :conversation_id)
|
|
249
|
+
|
|
250
|
+
# With custom configuration
|
|
251
|
+
intercom_config = FlowChat::Intercom::Configuration.get(:my_config)
|
|
252
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, intercom_config
|
|
253
|
+
|
|
254
|
+
# Additional webhook topics (e.g., to include admin events)
|
|
255
|
+
# Note: Default topics (user.created, user.replied) are always included
|
|
256
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, nil, [
|
|
257
|
+
"conversation.admin.assigned",
|
|
258
|
+
"conversation.admin.replied"
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
# Custom config AND additional webhook topics
|
|
262
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, intercom_config, [
|
|
263
|
+
"conversation.admin.assigned",
|
|
264
|
+
"conversation.admin.replied"
|
|
265
|
+
]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Default Webhook Topics:**
|
|
269
|
+
- `conversation.user.created` - New conversation started by user
|
|
270
|
+
- `conversation.user.replied` - User replied in existing conversation
|
|
271
|
+
|
|
272
|
+
**Additional Available Topics:**
|
|
273
|
+
- `conversation.admin.assigned` - Admin assigned to conversation
|
|
274
|
+
- `conversation.admin.replied` - Admin replied to conversation
|
|
275
|
+
- `conversation.admin.closed` - Admin closed conversation
|
|
276
|
+
- See [Intercom webhook docs](https://developers.intercom.com/docs/references/webhooks/webhook-models/) for full list
|
|
277
|
+
|
|
278
|
+
#### Webhook Setup
|
|
279
|
+
1. Add your HTTPS endpoint URL in Intercom Developer Hub → Configure → Webhooks
|
|
280
|
+
2. Intercom validates your endpoint with HEAD request (handled automatically)
|
|
281
|
+
3. Webhook notifications are validated using X-Hub-Signature with client_secret
|
|
282
|
+
|
|
283
|
+
#### FlowChat Client API
|
|
284
|
+
The FlowChat Intercom client provides only the core methods needed by the gateway:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
# Access within a flow via context
|
|
288
|
+
client = context["intercom.client"]
|
|
289
|
+
conversation_id = context["request.conversation_id"]
|
|
290
|
+
|
|
291
|
+
# Send a message (used by gateway automatically)
|
|
292
|
+
client.send_message(conversation_id, "Hello!", choices: nil, media: nil)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Note:** Conversations are automatically assigned to the configured admin when messages are sent (Intercom's default behavior). Each admin can change this in their personal settings if desired.
|
|
296
|
+
|
|
297
|
+
**Important:** For business logic (tags, assignment, state management, fetching conversations), use the official `intercom` gem directly in your application:
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
# In your application code, use the official gem for business logic
|
|
301
|
+
intercom = Intercom::Client.new(token: access_token)
|
|
302
|
+
|
|
303
|
+
# Tag management
|
|
304
|
+
intercom.tags.tag(name: "AI_HANDLING", conversations: [{id: conversation_id}])
|
|
305
|
+
|
|
306
|
+
# Assignment (override default auto-assignment)
|
|
307
|
+
intercom.conversations.reply(id: conversation_id, message_type: "assignment", admin_id: admin_id)
|
|
308
|
+
|
|
309
|
+
# State management
|
|
310
|
+
intercom.conversations.reply(id: conversation_id, message_type: "closed")
|
|
311
|
+
|
|
312
|
+
# Fetch conversation details
|
|
313
|
+
conversation = intercom.conversations.find(id: conversation_id)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Error Handling
|
|
317
|
+
- Rate limiting: `RateLimitError` with retry-after information
|
|
318
|
+
- Authentication: `ConfigurationError` for invalid tokens
|
|
319
|
+
- API errors: Proper handling for Intercom gem exceptions (ResourceNotFound, AuthenticationError, etc.)
|
|
320
|
+
|
|
321
|
+
## Instrumentation
|
|
322
|
+
|
|
323
|
+
FlowChat includes comprehensive instrumentation via `FlowChat::Instrumentation`:
|
|
324
|
+
- Flow execution events
|
|
325
|
+
- Session creation events
|
|
326
|
+
- Platform-specific metrics
|
|
327
|
+
- Error tracking and logging
|