flow_chat 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +11 -11
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/ussd_controller.rb +9 -9
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +16 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +10 -30
- data/lib/flow_chat/{base_executor.rb → executor.rb} +6 -11
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +81 -33
- data/lib/flow_chat/http/renderer.rb +3 -3
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +23 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +37 -35
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/middleware.rb +25 -9
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +24 -4
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +132 -96
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +84 -25
- data/docs/flows.md +0 -320
- data/docs/http-gateway-protocol.md +0 -432
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -146
- data/lib/flow_chat/http/app.rb +0 -6
- data/lib/flow_chat/http/middleware/executor.rb +0 -24
- data/lib/flow_chat/http/processor.rb +0 -33
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -96
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
require "middleware"
|
|
2
|
-
|
|
3
|
-
module FlowChat
|
|
4
|
-
class BaseProcessor
|
|
5
|
-
include FlowChat::Instrumentation
|
|
6
|
-
|
|
7
|
-
attr_reader :middleware
|
|
8
|
-
|
|
9
|
-
def initialize(controller, enable_simulator: nil)
|
|
10
|
-
FlowChat.logger.debug { "BaseProcessor: Initializing processor for controller #{controller.class.name}" }
|
|
11
|
-
|
|
12
|
-
@context = FlowChat::Context.new
|
|
13
|
-
@context["controller"] = controller
|
|
14
|
-
@context["enable_simulator"] = enable_simulator.nil? ? (defined?(Rails) && Rails.env.local?) : enable_simulator
|
|
15
|
-
@middleware = ::Middleware::Builder.new(name: middleware_name)
|
|
16
|
-
@session_options = FlowChat::Config.session
|
|
17
|
-
|
|
18
|
-
FlowChat.logger.debug { "BaseProcessor: Simulator mode #{@context["enable_simulator"] ? "enabled" : "disabled"}" }
|
|
19
|
-
|
|
20
|
-
yield self if block_given?
|
|
21
|
-
|
|
22
|
-
FlowChat.logger.debug { "BaseProcessor: Initialized #{self.class.name} successfully" }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def use_gateway(gateway_class, *args)
|
|
26
|
-
FlowChat.logger.debug { "BaseProcessor: Configuring gateway #{gateway_class.name} with args: #{args.inspect}" }
|
|
27
|
-
@gateway_class = gateway_class
|
|
28
|
-
@gateway_args = args
|
|
29
|
-
self
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def use_session_store(session_store)
|
|
33
|
-
raise "Session store must be a class" unless session_store.is_a?(Class)
|
|
34
|
-
FlowChat.logger.debug { "BaseProcessor: Configuring session store #{session_store.name}" }
|
|
35
|
-
@context["session.store"] = session_store
|
|
36
|
-
self
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def use_session_config(boundaries: nil, hash_identifiers: nil, identifier: nil)
|
|
40
|
-
FlowChat.logger.debug { "BaseProcessor: Configuring session config: boundaries=#{boundaries.inspect}, hash_identifiers=#{hash_identifiers}, identifier=#{identifier}" }
|
|
41
|
-
|
|
42
|
-
# Update the session options directly
|
|
43
|
-
@session_options = @session_options.dup
|
|
44
|
-
@session_options.boundaries = Array(boundaries) if boundaries != nil
|
|
45
|
-
@session_options.hash_identifiers = hash_identifiers if hash_identifiers != nil
|
|
46
|
-
@session_options.identifier = identifier if identifier != nil
|
|
47
|
-
|
|
48
|
-
self
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def use_middleware(middleware)
|
|
52
|
-
raise "Middleware must be a class" unless middleware.is_a?(Class)
|
|
53
|
-
FlowChat.logger.debug { "BaseProcessor: Adding middleware #{middleware.name}" }
|
|
54
|
-
@middleware.use middleware
|
|
55
|
-
self
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def use_cross_platform_sessions
|
|
59
|
-
FlowChat.logger.debug { "BaseProcessor: Enabling cross-platform sessions via session configuration" }
|
|
60
|
-
use_session_config(
|
|
61
|
-
boundaries: [:flow]
|
|
62
|
-
)
|
|
63
|
-
self
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def use_url_isolation
|
|
67
|
-
FlowChat.logger.debug { "BaseProcessor: Enabling URL-based session isolation" }
|
|
68
|
-
current_boundaries = @session_options.boundaries.dup
|
|
69
|
-
current_boundaries << :url unless current_boundaries.include?(:url)
|
|
70
|
-
use_session_config(boundaries: current_boundaries)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def run(flow_class, action, **options)
|
|
74
|
-
# Instrument flow execution (this will log via LogSubscriber)
|
|
75
|
-
instrument(Events::FLOW_EXECUTION_START, {
|
|
76
|
-
flow_name: flow_class.name.underscore,
|
|
77
|
-
action: action.to_s,
|
|
78
|
-
processor_type: self.class.name
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
@context["flow.name"] = flow_class.name.underscore
|
|
82
|
-
@context["flow.class"] = flow_class
|
|
83
|
-
@context["flow.action"] = action
|
|
84
|
-
@context["flow.options"] = options
|
|
85
|
-
|
|
86
|
-
FlowChat.logger.debug { "BaseProcessor: Context prepared for flow #{flow_class.name}" }
|
|
87
|
-
|
|
88
|
-
stack = build_middleware_stack
|
|
89
|
-
yield stack if block_given?
|
|
90
|
-
|
|
91
|
-
FlowChat.logger.debug { "BaseProcessor: Executing middleware stack for #{flow_class.name}##{action}" }
|
|
92
|
-
|
|
93
|
-
# Instrument flow execution with timing (this will log completion via LogSubscriber)
|
|
94
|
-
instrument(Events::FLOW_EXECUTION_END, {
|
|
95
|
-
flow_name: flow_class.name.underscore,
|
|
96
|
-
action: action.to_s,
|
|
97
|
-
processor_type: self.class.name
|
|
98
|
-
}) do
|
|
99
|
-
stack.call(@context)
|
|
100
|
-
end
|
|
101
|
-
rescue => error
|
|
102
|
-
FlowChat.logger.error { "BaseProcessor: Flow execution failed - #{flow_class.name}##{action}, Error: #{error.class.name}: #{error.message}" }
|
|
103
|
-
FlowChat.logger.debug { "BaseProcessor: Stack trace: #{error.backtrace.join("\n")}" }
|
|
104
|
-
|
|
105
|
-
# Instrument flow execution error (this will log error via LogSubscriber)
|
|
106
|
-
instrument(Events::FLOW_EXECUTION_ERROR, {
|
|
107
|
-
flow_name: flow_class.name.underscore,
|
|
108
|
-
action: action.to_s,
|
|
109
|
-
processor_type: self.class.name,
|
|
110
|
-
error_class: error.class.name,
|
|
111
|
-
error_message: error.message,
|
|
112
|
-
backtrace: error.backtrace&.first(10)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
raise
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
protected
|
|
119
|
-
|
|
120
|
-
# Subclasses should override these methods
|
|
121
|
-
def middleware_name
|
|
122
|
-
raise NotImplementedError, "Subclasses must implement middleware_name"
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def build_middleware_stack
|
|
126
|
-
raise NotImplementedError, "Subclasses must implement build_middleware_stack"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Helper method for building stacks
|
|
130
|
-
def create_middleware_stack(name)
|
|
131
|
-
raise ArgumentError, "Gateway is required. Call use_gateway(gateway_class, *args) before running." unless @gateway_class
|
|
132
|
-
|
|
133
|
-
::Middleware::Builder.new(name: name) do |b|
|
|
134
|
-
b.use @gateway_class, *@gateway_args
|
|
135
|
-
b.use FlowChat::Session::Middleware, @session_options
|
|
136
|
-
configure_middleware_stack(b)
|
|
137
|
-
end.inject_logger(FlowChat.logger)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def configure_middleware_stack(builder)
|
|
141
|
-
raise NotImplementedError, "Subclasses must implement configure_middleware_stack"
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
attr_reader :context
|
|
145
|
-
end
|
|
146
|
-
end
|
data/lib/flow_chat/http/app.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
require_relative "../../base_executor"
|
|
2
|
-
|
|
3
|
-
module FlowChat
|
|
4
|
-
module Http
|
|
5
|
-
module Middleware
|
|
6
|
-
class Executor < FlowChat::BaseExecutor
|
|
7
|
-
protected
|
|
8
|
-
|
|
9
|
-
def platform_name
|
|
10
|
-
"HTTP"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def log_prefix
|
|
14
|
-
"Http::Executor"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def build_platform_app(context)
|
|
18
|
-
FlowChat.logger.debug { "#{log_prefix}: Building HTTP app instance" }
|
|
19
|
-
FlowChat::Http::App.new(context)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Http
|
|
3
|
-
class Processor < FlowChat::BaseProcessor
|
|
4
|
-
def use_durable_sessions(cross_gateway: false)
|
|
5
|
-
FlowChat.logger.debug { "Http::Processor: Enabling durable sessions via session configuration" }
|
|
6
|
-
use_session_config(
|
|
7
|
-
identifier: :user_id
|
|
8
|
-
)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
protected
|
|
12
|
-
|
|
13
|
-
def middleware_name
|
|
14
|
-
"http.middleware"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def build_middleware_stack
|
|
18
|
-
FlowChat.logger.debug { "Http::Processor: Building HTTP middleware stack" }
|
|
19
|
-
create_middleware_stack("http")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def configure_middleware_stack(builder)
|
|
23
|
-
FlowChat.logger.debug { "Http::Processor: Configuring HTTP middleware stack" }
|
|
24
|
-
|
|
25
|
-
builder.use middleware
|
|
26
|
-
FlowChat.logger.debug { "Http::Processor: Added custom middleware" }
|
|
27
|
-
|
|
28
|
-
builder.use FlowChat::Http::Middleware::Executor
|
|
29
|
-
FlowChat.logger.debug { "Http::Processor: Added Http::Middleware::Executor" }
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Session
|
|
3
|
-
class RailsSessionStore
|
|
4
|
-
include FlowChat::Instrumentation
|
|
5
|
-
|
|
6
|
-
# Make context available for instrumentation enrichment
|
|
7
|
-
attr_reader :context
|
|
8
|
-
|
|
9
|
-
def initialize(context)
|
|
10
|
-
@context = context
|
|
11
|
-
@session_id = context["session.id"]
|
|
12
|
-
@session_store = context.controller.session
|
|
13
|
-
@session_data = (session_store[session_id] || {}).with_indifferent_access
|
|
14
|
-
|
|
15
|
-
FlowChat.logger.debug { "RailsSessionStore: Initialized Rails session store for session #{session_id}" }
|
|
16
|
-
FlowChat.logger.debug { "RailsSessionStore: Loaded session data with #{session_data.keys.size} keys" }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def get(key)
|
|
20
|
-
value = session_data[key]
|
|
21
|
-
|
|
22
|
-
# Use instrumentation for data get
|
|
23
|
-
instrument(Events::SESSION_DATA_GET, {
|
|
24
|
-
session_id: session_id,
|
|
25
|
-
key: key.to_s,
|
|
26
|
-
value: value
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
value
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def set(key, value)
|
|
33
|
-
FlowChat.logger.debug { "RailsSessionStore: Setting key '#{key}' = #{value.inspect} in session #{session_id}" }
|
|
34
|
-
|
|
35
|
-
session_data[key] = value
|
|
36
|
-
session_store[session_id] = session_data
|
|
37
|
-
|
|
38
|
-
# Use instrumentation for data set
|
|
39
|
-
instrument(Events::SESSION_DATA_SET, {
|
|
40
|
-
session_id: session_id,
|
|
41
|
-
key: key.to_s
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
FlowChat.logger.debug { "RailsSessionStore: Session data saved to Rails session store" }
|
|
45
|
-
value
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def delete(key)
|
|
49
|
-
FlowChat.logger.debug { "RailsSessionStore: Deleting key '#{key}' from session #{session_id}" }
|
|
50
|
-
set key, nil
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def destroy
|
|
54
|
-
# Use instrumentation for session destruction
|
|
55
|
-
instrument(Events::SESSION_DESTROYED, {
|
|
56
|
-
session_id: session_id,
|
|
57
|
-
gateway: "rails" # Rails doesn't have a specific gateway context
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
session_store[session_id] = nil
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
attr_reader :session_id, :session_store, :session_data
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
data/lib/flow_chat/ussd/app.rb
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Ussd
|
|
3
|
-
module Gateway
|
|
4
|
-
class Nsano
|
|
5
|
-
include FlowChat::Instrumentation
|
|
6
|
-
|
|
7
|
-
attr_reader :context
|
|
8
|
-
|
|
9
|
-
def initialize(app)
|
|
10
|
-
@app = app
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def call(context)
|
|
14
|
-
@context = context
|
|
15
|
-
controller = context["controller"]
|
|
16
|
-
controller.request
|
|
17
|
-
|
|
18
|
-
# Add timestamp for all requests
|
|
19
|
-
context["request.timestamp"] = Time.current.iso8601
|
|
20
|
-
|
|
21
|
-
# Set a basic message_id (can be enhanced based on actual Nsano implementation)
|
|
22
|
-
context["request.message_id"] = SecureRandom.uuid
|
|
23
|
-
context["request.platform"] = :ussd
|
|
24
|
-
|
|
25
|
-
# TODO: Implement Nsano-specific parameter parsing
|
|
26
|
-
# For now, add basic instrumentation structure for when this is implemented
|
|
27
|
-
|
|
28
|
-
# Placeholder instrumentation - indicates Nsano implementation is needed
|
|
29
|
-
instrument(Events::MESSAGE_RECEIVED, {
|
|
30
|
-
from: "TODO", # Would be parsed from Nsano params
|
|
31
|
-
message: "TODO", # Would be actual user input
|
|
32
|
-
session_id: "TODO", # Would be Nsano session ID
|
|
33
|
-
gateway: :nsano,
|
|
34
|
-
platform: :ussd,
|
|
35
|
-
timestamp: context["request.timestamp"]
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
# Process request with placeholder app call
|
|
39
|
-
_, _, _, _ = @app.call(context) if @app
|
|
40
|
-
|
|
41
|
-
# Placeholder response instrumentation
|
|
42
|
-
instrument(Events::MESSAGE_SENT, {
|
|
43
|
-
to: "TODO", # Would be actual phone number
|
|
44
|
-
session_id: "TODO", # Would be Nsano session ID
|
|
45
|
-
message: "TODO", # Would be actual response message
|
|
46
|
-
message_type: "prompt", # Would depend on actual response type
|
|
47
|
-
gateway: :nsano,
|
|
48
|
-
platform: :ussd,
|
|
49
|
-
content_length: 0, # Would be actual content length
|
|
50
|
-
timestamp: context["request.timestamp"]
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
# input = context["rack.input"].read
|
|
54
|
-
# context["rack.input"].rewind
|
|
55
|
-
# if input.present?
|
|
56
|
-
# params = JSON.parse input
|
|
57
|
-
# if params["network"].present? && params["UserSessionID"].present?
|
|
58
|
-
# request_id = "nsano::request_id::#{params["UserSessionID"]}"
|
|
59
|
-
# context["ussd.request"] = {
|
|
60
|
-
# gateway: :nsano,
|
|
61
|
-
# network: params["network"].to_sym,
|
|
62
|
-
# msisdn: Phonelib.parse(params["msisdn"]).e164,
|
|
63
|
-
# type: Config.cache&.read(request_id).present? ? :response : :initial,
|
|
64
|
-
# input: params["msg"].presence,
|
|
65
|
-
# network: params["network"]
|
|
66
|
-
# }
|
|
67
|
-
# end
|
|
68
|
-
# end
|
|
69
|
-
|
|
70
|
-
# status, headers, response = @app.call(context)
|
|
71
|
-
|
|
72
|
-
# if context["ussd.response"].present? && context["ussd.request"][:gateway] == :nsano
|
|
73
|
-
# if context["ussd.response"][:type] == :terminal
|
|
74
|
-
# Config.cache&.write(request_id, nil)
|
|
75
|
-
# else
|
|
76
|
-
# Config.cache&.write(request_id, 1)
|
|
77
|
-
# end
|
|
78
|
-
|
|
79
|
-
# status = 200
|
|
80
|
-
# response =
|
|
81
|
-
# {
|
|
82
|
-
# USSDResp: {
|
|
83
|
-
# action: (context["ussd.response"][:type] == :terminal) ? :prompt : :input,
|
|
84
|
-
# menus: "",
|
|
85
|
-
# title: context["ussd.response"][:body]
|
|
86
|
-
# }
|
|
87
|
-
# }.to_json
|
|
88
|
-
# headers = headers.merge({"Content-Type" => "application/json", "Content-Length" => response.bytesize.to_s})
|
|
89
|
-
# response = [response]
|
|
90
|
-
# end
|
|
91
|
-
# [status, headers, response]
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
require_relative "../../base_executor"
|
|
2
|
-
|
|
3
|
-
module FlowChat
|
|
4
|
-
module Ussd
|
|
5
|
-
module Middleware
|
|
6
|
-
class Executor < FlowChat::BaseExecutor
|
|
7
|
-
protected
|
|
8
|
-
|
|
9
|
-
def platform_name
|
|
10
|
-
"USSD"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def log_prefix
|
|
14
|
-
"Ussd::Executor"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def build_platform_app(context)
|
|
18
|
-
FlowChat.logger.debug { "#{log_prefix}: Building USSD app instance" }
|
|
19
|
-
FlowChat::Ussd::App.new(context)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Ussd
|
|
3
|
-
class Processor < FlowChat::BaseProcessor
|
|
4
|
-
def use_durable_sessions(cross_gateway: false)
|
|
5
|
-
FlowChat.logger.debug { "Ussd::Processor: Enabling durable sessions via session configuration" }
|
|
6
|
-
use_session_config(
|
|
7
|
-
identifier: :msisdn # Use MSISDN for durable sessions
|
|
8
|
-
)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
protected
|
|
12
|
-
|
|
13
|
-
def middleware_name
|
|
14
|
-
"ussd.middleware"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def build_middleware_stack
|
|
18
|
-
FlowChat.logger.debug { "Ussd::Processor: Building USSD middleware stack" }
|
|
19
|
-
create_middleware_stack("ussd")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def configure_middleware_stack(builder)
|
|
23
|
-
FlowChat.logger.debug { "Ussd::Processor: Configuring USSD middleware stack" }
|
|
24
|
-
|
|
25
|
-
builder.use FlowChat::Ussd::Middleware::Pagination
|
|
26
|
-
FlowChat.logger.debug { "Ussd::Processor: Added Ussd::Middleware::Pagination" }
|
|
27
|
-
|
|
28
|
-
builder.use middleware
|
|
29
|
-
FlowChat.logger.debug { "Ussd::Processor: Added custom middleware" }
|
|
30
|
-
|
|
31
|
-
builder.use FlowChat::Ussd::Middleware::ChoiceMapper
|
|
32
|
-
FlowChat.logger.debug { "Ussd::Processor: Added Ussd::Middleware::ChoiceMapper" }
|
|
33
|
-
|
|
34
|
-
builder.use FlowChat::Ussd::Middleware::Executor
|
|
35
|
-
FlowChat.logger.debug { "Ussd::Processor: Added Ussd::Middleware::Executor" }
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Whatsapp
|
|
3
|
-
class App < FlowChat::BaseApp
|
|
4
|
-
def contact_name
|
|
5
|
-
context["request.contact_name"]
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
def location
|
|
9
|
-
context["request.location"]
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def media
|
|
13
|
-
context["request.media"]
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
protected
|
|
17
|
-
|
|
18
|
-
# WhatsApp has special startup logic and supports media
|
|
19
|
-
def prepare_user_input
|
|
20
|
-
user_input = input
|
|
21
|
-
if session.get("$started_at$").nil?
|
|
22
|
-
session.set("$started_at$", Time.current.iso8601)
|
|
23
|
-
user_input = nil
|
|
24
|
-
end
|
|
25
|
-
user_input
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
require_relative "../../base_executor"
|
|
2
|
-
|
|
3
|
-
module FlowChat
|
|
4
|
-
module Whatsapp
|
|
5
|
-
module Middleware
|
|
6
|
-
class Executor < FlowChat::BaseExecutor
|
|
7
|
-
protected
|
|
8
|
-
|
|
9
|
-
def platform_name
|
|
10
|
-
"WhatsApp"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def log_prefix
|
|
14
|
-
"Whatsapp::Executor"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def build_platform_app(context)
|
|
18
|
-
FlowChat.logger.debug { "#{log_prefix}: Building WhatsApp app instance" }
|
|
19
|
-
FlowChat::Whatsapp::App.new(context)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Whatsapp
|
|
3
|
-
class Processor < FlowChat::BaseProcessor
|
|
4
|
-
def use_whatsapp_config(config)
|
|
5
|
-
FlowChat.logger.debug { "Whatsapp::Processor: Configuring WhatsApp config: #{config.class.name}" }
|
|
6
|
-
@whatsapp_config = config
|
|
7
|
-
self
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
protected
|
|
11
|
-
|
|
12
|
-
def middleware_name
|
|
13
|
-
"whatsapp.middleware"
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def build_middleware_stack
|
|
17
|
-
FlowChat.logger.debug { "Whatsapp::Processor: Building WhatsApp middleware stack" }
|
|
18
|
-
create_middleware_stack("whatsapp")
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def configure_middleware_stack(builder)
|
|
22
|
-
FlowChat.logger.debug { "Whatsapp::Processor: Configuring WhatsApp middleware stack" }
|
|
23
|
-
|
|
24
|
-
builder.use middleware
|
|
25
|
-
FlowChat.logger.debug { "Whatsapp::Processor: Added custom middleware" }
|
|
26
|
-
|
|
27
|
-
builder.use FlowChat::Whatsapp::Middleware::Executor
|
|
28
|
-
FlowChat.logger.debug { "Whatsapp::Processor: Added Whatsapp::Middleware::Executor" }
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
module FlowChat
|
|
2
|
-
module Whatsapp
|
|
3
|
-
# Module to be included in background jobs for WhatsApp response delivery
|
|
4
|
-
# Only handles sending responses, not processing flows
|
|
5
|
-
module SendJobSupport
|
|
6
|
-
extend ActiveSupport::Concern
|
|
7
|
-
|
|
8
|
-
included do
|
|
9
|
-
# Set up job configuration
|
|
10
|
-
queue_as :default
|
|
11
|
-
retry_on StandardError, wait: :exponentially_longer, attempts: 3
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Main job execution method for sending responses
|
|
15
|
-
def perform_whatsapp_send(send_data)
|
|
16
|
-
config = resolve_whatsapp_config(send_data)
|
|
17
|
-
client = FlowChat::Whatsapp::Client.new(config)
|
|
18
|
-
|
|
19
|
-
result = client.send_message(send_data[:msisdn], send_data[:response])
|
|
20
|
-
|
|
21
|
-
if result
|
|
22
|
-
Rails.logger.info "WhatsApp message sent successfully: #{result["messages"]&.first&.dig("id")}"
|
|
23
|
-
on_whatsapp_send_success(send_data, result)
|
|
24
|
-
else
|
|
25
|
-
Rails.logger.error "Failed to send WhatsApp message to #{send_data[:msisdn]}"
|
|
26
|
-
raise "WhatsApp API call failed"
|
|
27
|
-
end
|
|
28
|
-
rescue => e
|
|
29
|
-
on_whatsapp_send_error(e, send_data)
|
|
30
|
-
handle_whatsapp_send_error(e, send_data, config)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
private
|
|
34
|
-
|
|
35
|
-
# Resolve WhatsApp configuration by name or fallback
|
|
36
|
-
def resolve_whatsapp_config(send_data)
|
|
37
|
-
# Try to resolve by name first (preferred method)
|
|
38
|
-
if send_data[:config_name] && FlowChat::Whatsapp::Configuration.exists?(send_data[:config_name])
|
|
39
|
-
return FlowChat::Whatsapp::Configuration.get(send_data[:config_name])
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Final fallback to default configuration
|
|
43
|
-
FlowChat::Whatsapp::Configuration.from_credentials
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# Handle errors with user notification
|
|
47
|
-
def handle_whatsapp_send_error(error, send_data, config = nil)
|
|
48
|
-
Rails.logger.error "WhatsApp send job error: #{error.message}"
|
|
49
|
-
Rails.logger.error error.backtrace&.join("\n") if error.backtrace
|
|
50
|
-
|
|
51
|
-
# Try to send error message to user if we have config
|
|
52
|
-
if config
|
|
53
|
-
begin
|
|
54
|
-
client = FlowChat::Whatsapp::Client.new(config)
|
|
55
|
-
client.send_text(
|
|
56
|
-
send_data[:msisdn],
|
|
57
|
-
"⚠️ We're experiencing technical difficulties. Please try again in a few minutes."
|
|
58
|
-
)
|
|
59
|
-
rescue => send_error
|
|
60
|
-
Rails.logger.error "Failed to send error message: #{send_error.message}"
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Re-raise for job retry logic
|
|
65
|
-
raise error
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Override this method in your job for custom behavior
|
|
69
|
-
def on_whatsapp_send_success(send_data, result)
|
|
70
|
-
# Optional callback for successful message sending
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Override this method in your job for custom error handling
|
|
74
|
-
def on_whatsapp_send_error(error, send_data)
|
|
75
|
-
# Optional callback for error handling
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|