flow_chat 0.8.1 → 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 +154 -0
- 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/simulator_controller.rb +21 -1
- data/examples/ussd_controller.rb +10 -10
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +23 -28
- data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
- 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 +125 -0
- data/lib/flow_chat/http/renderer.rb +41 -0
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +25 -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 +49 -0
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/cache_session_store.rb +1 -17
- data/lib/flow_chat/session/middleware.rb +43 -26
- data/lib/flow_chat/simulator/controller.rb +17 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
- 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 +27 -11
- 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 +144 -106
- 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 +88 -21
- data/docs/flows.md +0 -320
- 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 -145
- 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 -98
- 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
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
require "middleware"
|
|
2
|
+
|
|
3
|
+
module FlowChat
|
|
4
|
+
class Processor
|
|
5
|
+
include FlowChat::Instrumentation
|
|
6
|
+
|
|
7
|
+
attr_reader :custom_middleware_builder, :context, :async_job_class, :async_job_params
|
|
8
|
+
|
|
9
|
+
def initialize(controller, enable_simulator: nil)
|
|
10
|
+
FlowChat.logger.debug { "Processor: 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
|
+
@custom_middleware_builder = ::Middleware::Builder.new(name: "processor.custom_middleware_builder")
|
|
16
|
+
@session_options = FlowChat::Config.session
|
|
17
|
+
@async_job_class = nil
|
|
18
|
+
@async_job_params = {}
|
|
19
|
+
|
|
20
|
+
FlowChat.logger.debug { "Processor: Simulator mode #{@context["enable_simulator"] ? "enabled" : "disabled"}" }
|
|
21
|
+
|
|
22
|
+
yield self if block_given?
|
|
23
|
+
|
|
24
|
+
FlowChat.logger.debug { "Processor: Initialized #{self.class.name} successfully" }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def use_gateway(gateway_class, *args)
|
|
28
|
+
FlowChat.logger.debug { "Processor: Configuring gateway #{gateway_class.name} with args: #{args.inspect}" }
|
|
29
|
+
@gateway_class = gateway_class
|
|
30
|
+
@gateway_args = args
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def use_session_store(session_store)
|
|
35
|
+
raise "Session store must be a class" unless session_store.is_a?(Class)
|
|
36
|
+
FlowChat.logger.debug { "Processor: Configuring session store #{session_store.name}" }
|
|
37
|
+
@context["session.store"] = session_store
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def use_session_config(boundaries: nil, hash_identifiers: nil, identifier: nil, &block)
|
|
42
|
+
if block_given?
|
|
43
|
+
FlowChat.logger.debug { "Processor: Configuring session config with custom proc" }
|
|
44
|
+
@session_options = @session_options.dup
|
|
45
|
+
@session_options.session_id_proc = block
|
|
46
|
+
else
|
|
47
|
+
FlowChat.logger.debug { "Processor: Configuring session config: boundaries=#{boundaries.inspect}, hash_identifiers=#{hash_identifiers}, identifier=#{identifier}" }
|
|
48
|
+
|
|
49
|
+
# Update the session options directly
|
|
50
|
+
@session_options = @session_options.dup
|
|
51
|
+
@session_options.boundaries = Array(boundaries) unless boundaries.nil?
|
|
52
|
+
@session_options.hash_identifiers = hash_identifiers unless hash_identifiers.nil?
|
|
53
|
+
@session_options.identifier = identifier unless identifier.nil?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def use_middleware(middleware)
|
|
60
|
+
if block_given?
|
|
61
|
+
yield custom_middleware_builder
|
|
62
|
+
return self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
raise "Middleware must be a class" unless middleware.is_a?(Class)
|
|
66
|
+
FlowChat.logger.debug { "Processor: Adding custom middleware: #{middleware.name}" }
|
|
67
|
+
custom_middleware_builder.use middleware
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def use_cross_platform_sessions
|
|
72
|
+
FlowChat.logger.debug { "Processor: Enabling cross-platform sessions via session configuration" }
|
|
73
|
+
use_session_config(
|
|
74
|
+
boundaries: [:flow]
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def use_url_isolation
|
|
79
|
+
FlowChat.logger.debug { "Processor: Enabling URL-based session isolation" }
|
|
80
|
+
current_boundaries = @session_options.boundaries.dup
|
|
81
|
+
current_boundaries << :url unless current_boundaries.include?(:url)
|
|
82
|
+
use_session_config(boundaries: current_boundaries)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def use_durable_sessions(cross_gateway: false)
|
|
86
|
+
FlowChat.logger.debug { "Processor: Enabling durable sessions via session configuration" }
|
|
87
|
+
use_session_config(
|
|
88
|
+
identifier: :user_id
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def use_async(job_class = nil, **job_params)
|
|
93
|
+
# If no job class provided, use GenericAsyncJob with factory param
|
|
94
|
+
if job_class.nil?
|
|
95
|
+
unless job_params.key?(:factory)
|
|
96
|
+
raise ArgumentError, "When use_async is called without a job class, factory: parameter is required"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
FlowChat.logger.debug { "Processor: Configuring async processing with GenericAsyncJob for factory '#{job_params[:factory]}'" }
|
|
100
|
+
@async_job_class = FlowChat::GenericAsyncJob
|
|
101
|
+
else
|
|
102
|
+
FlowChat.logger.debug { "Processor: Configuring async processing with job class #{job_class.name} and params: #{job_params.inspect}" }
|
|
103
|
+
@async_job_class = job_class
|
|
104
|
+
end
|
|
105
|
+
@async_job_params = job_params
|
|
106
|
+
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def async_enabled?
|
|
111
|
+
!@async_job_class.nil?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def run(flow_class, action, **options)
|
|
115
|
+
# Instrument flow execution (this will log via LogSubscriber)
|
|
116
|
+
instrument(Events::FLOW_EXECUTION_START, {
|
|
117
|
+
flow_name: flow_class.name.underscore,
|
|
118
|
+
action: action.to_s,
|
|
119
|
+
processor_type: self.class.name
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
@context["processor"] = self
|
|
123
|
+
@context["flow.name"] = flow_class.name.underscore
|
|
124
|
+
@context["flow.class"] = flow_class
|
|
125
|
+
@context["flow.action"] = action
|
|
126
|
+
@context["flow.options"] = options
|
|
127
|
+
|
|
128
|
+
FlowChat.logger.debug { "Processor: Context prepared for flow #{flow_class.name}" }
|
|
129
|
+
|
|
130
|
+
stack = create_middleware_stack
|
|
131
|
+
yield stack if block_given?
|
|
132
|
+
|
|
133
|
+
FlowChat.logger.debug { "Processor: Executing middleware stack for #{flow_class.name}##{action}" }
|
|
134
|
+
|
|
135
|
+
# Instrument flow execution with timing (this will log completion via LogSubscriber)
|
|
136
|
+
instrument(Events::FLOW_EXECUTION_END, {
|
|
137
|
+
flow_name: flow_class.name.underscore,
|
|
138
|
+
action: action.to_s
|
|
139
|
+
}) do
|
|
140
|
+
stack.call(@context)
|
|
141
|
+
end
|
|
142
|
+
rescue => error
|
|
143
|
+
FlowChat.logger.error { "Processor: Flow execution failed - #{flow_class.name}##{action}, Error: #{error.class.name}: #{error.message}" }
|
|
144
|
+
FlowChat.logger.debug { "Processor: Stack trace: #{error.backtrace.join("\n")}" }
|
|
145
|
+
|
|
146
|
+
# Instrument flow execution error (this will log error via LogSubscriber)
|
|
147
|
+
instrument(Events::FLOW_EXECUTION_ERROR, {
|
|
148
|
+
flow_name: flow_class.name.underscore,
|
|
149
|
+
action: action.to_s,
|
|
150
|
+
error_class: error.class.name,
|
|
151
|
+
error_message: error.message,
|
|
152
|
+
backtrace: error.backtrace&.first(10)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
raise
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
protected
|
|
159
|
+
|
|
160
|
+
# Helper method for building stacks
|
|
161
|
+
def create_middleware_stack
|
|
162
|
+
raise ArgumentError, "Gateway is required. Call use_gateway(gateway_class, *args) before running." unless @gateway_class
|
|
163
|
+
|
|
164
|
+
middleware_stack = ::Middleware::Builder.new(name: @gateway_class.name) do |b|
|
|
165
|
+
# Gateway always comes first
|
|
166
|
+
b.use @gateway_class, *@gateway_args
|
|
167
|
+
# Session middleware next. We need to setup our session identifiers
|
|
168
|
+
b.use FlowChat::Session::Middleware, @session_options
|
|
169
|
+
|
|
170
|
+
if @gateway_class.respond_to?(:configure_middleware_stack)
|
|
171
|
+
FlowChat.logger.debug { "Processor: Using platform specific middleware configuration" }
|
|
172
|
+
@gateway_class.configure_middleware_stack(b, custom_middleware_builder)
|
|
173
|
+
else
|
|
174
|
+
b.use custom_middleware_builder
|
|
175
|
+
FlowChat.logger.debug { "Processor: Added custom middleware" }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Executor always goes last.
|
|
179
|
+
# Nothing can execute after it.
|
|
180
|
+
b.use FlowChat::Executor
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
middleware_stack.inject_logger(FlowChat.logger) if FlowChat::Config.inject_middleware_logger
|
|
184
|
+
|
|
185
|
+
middleware_stack
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "kramdown"
|
|
2
|
+
require "rails-html-sanitizer"
|
|
3
|
+
|
|
4
|
+
module FlowChat
|
|
5
|
+
module Renderers
|
|
6
|
+
module MarkdownSupport
|
|
7
|
+
def to_html(text)
|
|
8
|
+
return "" if text.nil?
|
|
9
|
+
|
|
10
|
+
html = Kramdown::Document.new(text.to_s, **kramdown_options).to_html.strip
|
|
11
|
+
sanitize_html(html)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def sanitize_html(html)
|
|
15
|
+
sanitized = self.class.sanitizer.sanitize(
|
|
16
|
+
html,
|
|
17
|
+
tags: allowed_tags,
|
|
18
|
+
attributes: allowed_attributes
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
post_process_html(sanitized)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.included(base)
|
|
25
|
+
base.extend(ClassMethods)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ClassMethods
|
|
29
|
+
def sanitizer
|
|
30
|
+
@sanitizer ||= Rails::Html::SafeListSanitizer.new
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Override in subclasses to customize Kramdown options
|
|
37
|
+
# Default uses straight quotes (ASCII 39/34) instead of curly smart quotes
|
|
38
|
+
def kramdown_options
|
|
39
|
+
{smart_quotes: [39, 39, 34, 34]}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Override in subclasses to specify allowed HTML tags
|
|
43
|
+
def allowed_tags
|
|
44
|
+
%w[b strong i em a code pre]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Override in subclasses to specify allowed HTML attributes
|
|
48
|
+
def allowed_attributes
|
|
49
|
+
%w[href]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Override in subclasses to post-process sanitized HTML
|
|
53
|
+
def post_process_html(html)
|
|
54
|
+
html
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -111,23 +111,7 @@ module FlowChat
|
|
|
111
111
|
private
|
|
112
112
|
|
|
113
113
|
def session_key
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
gateway = @context["request.gateway"]
|
|
117
|
-
msisdn = @context["request.msisdn"]
|
|
118
|
-
|
|
119
|
-
key = case gateway
|
|
120
|
-
when :whatsapp_cloud_api
|
|
121
|
-
"flow_chat:session:whatsapp:#{msisdn}"
|
|
122
|
-
when :nalo, :nsano
|
|
123
|
-
session_id = @context["request.id"]
|
|
124
|
-
"flow_chat:session:ussd:#{session_id}:#{msisdn}"
|
|
125
|
-
else
|
|
126
|
-
"flow_chat:session:unknown:#{msisdn}"
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
FlowChat.logger.debug { "CacheSessionStore: Generated session key: #{key}" }
|
|
130
|
-
key
|
|
114
|
+
"flow_chat:cached_session:#{@context["session.id"]}"
|
|
131
115
|
end
|
|
132
116
|
|
|
133
117
|
def session_ttl
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
|
|
1
3
|
module FlowChat
|
|
2
4
|
module Session
|
|
3
5
|
class Middleware
|
|
@@ -52,6 +54,14 @@ module FlowChat
|
|
|
52
54
|
return session_id
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# Check for custom session ID proc
|
|
58
|
+
if @session_options.session_id_proc
|
|
59
|
+
FlowChat.logger.debug { "Session::Middleware: Using custom session ID proc" }
|
|
60
|
+
session_id = @session_options.session_id_proc.call(context)
|
|
61
|
+
FlowChat.logger.debug { "Session::Middleware: Generated custom session ID: #{session_id}" }
|
|
62
|
+
return session_id
|
|
63
|
+
end
|
|
64
|
+
|
|
55
65
|
FlowChat.logger.debug { "Session::Middleware: Building session ID for platform=#{platform}, gateway=#{gateway}, flow=#{flow_name}" }
|
|
56
66
|
|
|
57
67
|
# Get identifier based on configuration
|
|
@@ -64,32 +74,33 @@ module FlowChat
|
|
|
64
74
|
end
|
|
65
75
|
|
|
66
76
|
def get_session_identifier(context)
|
|
67
|
-
identifier_type = @session_options.identifier
|
|
68
|
-
|
|
69
|
-
# If no identifier specified, use platform defaults
|
|
70
|
-
if identifier_type.nil?
|
|
71
|
-
platform = context["request.platform"]
|
|
72
|
-
identifier_type = case platform
|
|
73
|
-
when :ussd
|
|
74
|
-
:request_id # USSD defaults to ephemeral sessions
|
|
75
|
-
when :whatsapp
|
|
76
|
-
:msisdn # WhatsApp defaults to durable sessions
|
|
77
|
-
else
|
|
78
|
-
:msisdn # Default fallback to durable
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
77
|
+
identifier_type = @session_options.identifier || platform_default_identifier(context)
|
|
78
|
+
|
|
82
79
|
case identifier_type
|
|
83
80
|
when :request_id
|
|
84
81
|
context["request.id"]
|
|
82
|
+
when :user_id
|
|
83
|
+
user_id = context["request.user_id"]
|
|
84
|
+
@session_options.hash_identifiers ? hash_identifier(user_id) : user_id
|
|
85
85
|
when :msisdn
|
|
86
|
-
|
|
87
|
-
@session_options.
|
|
86
|
+
msisdn = context["request.msisdn"]
|
|
87
|
+
@session_options.hash_identifiers ? hash_identifier(msisdn) : msisdn
|
|
88
88
|
else
|
|
89
89
|
raise "Invalid session identifier type: #{identifier_type}"
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
def platform_default_identifier(context)
|
|
94
|
+
platform = context["request.platform"]
|
|
95
|
+
|
|
96
|
+
case platform
|
|
97
|
+
when :whatsapp
|
|
98
|
+
:msisdn
|
|
99
|
+
else
|
|
100
|
+
:request_id
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
93
104
|
def build_session_id(flow_name, platform, gateway, identifier)
|
|
94
105
|
parts = []
|
|
95
106
|
|
|
@@ -120,19 +131,26 @@ module FlowChat
|
|
|
120
131
|
return nil unless request
|
|
121
132
|
|
|
122
133
|
# Extract host and path for URL boundary
|
|
123
|
-
host =
|
|
124
|
-
|
|
134
|
+
host = begin
|
|
135
|
+
request.host
|
|
136
|
+
rescue
|
|
137
|
+
nil
|
|
138
|
+
end
|
|
139
|
+
path = begin
|
|
140
|
+
request.path
|
|
141
|
+
rescue
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
125
144
|
|
|
126
|
-
# Create a normalized URL identifier: host + path
|
|
145
|
+
# Create a normalized URL identifier: host + path
|
|
127
146
|
# e.g., "example.com/api/v1/ussd" or "tenant1.example.com/ussd"
|
|
128
147
|
url_parts = []
|
|
129
148
|
url_parts << host if host.present?
|
|
130
|
-
url_parts << path.sub(/^\//,
|
|
149
|
+
url_parts << path.sub(/^\//, "") if path.present? && path != "/"
|
|
131
150
|
|
|
132
151
|
# For long URLs, use first part + hash suffix instead of full hash
|
|
133
|
-
url_identifier = url_parts.join(
|
|
152
|
+
url_identifier = url_parts.join("/").gsub(/[^a-zA-Z0-9._-]/, "_")
|
|
134
153
|
if url_identifier.length > 50
|
|
135
|
-
require 'digest'
|
|
136
154
|
# Take first 41 chars + hash suffix to keep it manageable but recognizable
|
|
137
155
|
first_part = url_identifier[0, 41]
|
|
138
156
|
hash_suffix = Digest::SHA256.hexdigest(url_identifier)[0, 8]
|
|
@@ -142,10 +160,9 @@ module FlowChat
|
|
|
142
160
|
url_identifier
|
|
143
161
|
end
|
|
144
162
|
|
|
145
|
-
def
|
|
163
|
+
def hash_identifier(identifier)
|
|
146
164
|
# Use SHA256 but only take first 8 characters for reasonable session IDs
|
|
147
|
-
|
|
148
|
-
Digest::SHA256.hexdigest(phone.to_s)[0, 8]
|
|
165
|
+
Digest::SHA256.hexdigest(identifier.to_s)[0, 8]
|
|
149
166
|
end
|
|
150
167
|
end
|
|
151
168
|
end
|
|
@@ -23,12 +23,12 @@ module FlowChat
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def default_config_key
|
|
26
|
-
|
|
26
|
+
:ussd
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def configurations
|
|
30
30
|
{
|
|
31
|
-
|
|
31
|
+
ussd: {
|
|
32
32
|
name: "USSD (Nalo)",
|
|
33
33
|
description: "USSD integration using Nalo",
|
|
34
34
|
processor_type: "ussd",
|
|
@@ -41,7 +41,7 @@ module FlowChat
|
|
|
41
41
|
session_timeout: 300
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
|
|
44
|
+
whatsapp: {
|
|
45
45
|
name: "WhatsApp (Cloud API)",
|
|
46
46
|
description: "WhatsApp integration using Cloud API",
|
|
47
47
|
processor_type: "whatsapp",
|
|
@@ -53,6 +53,18 @@ module FlowChat
|
|
|
53
53
|
phone_number: default_phone_number,
|
|
54
54
|
contact_name: default_contact_name
|
|
55
55
|
}
|
|
56
|
+
},
|
|
57
|
+
http: {
|
|
58
|
+
name: "HTTP API",
|
|
59
|
+
description: "HTTP integration with JSON request/response",
|
|
60
|
+
processor_type: "http",
|
|
61
|
+
gateway: "http_simple",
|
|
62
|
+
endpoint: "/http/webhook",
|
|
63
|
+
icon: "🌐",
|
|
64
|
+
color: "#0066cc",
|
|
65
|
+
settings: {
|
|
66
|
+
user_id: default_phone_number
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
end
|
|
@@ -71,7 +83,7 @@ module FlowChat
|
|
|
71
83
|
default_phone_number: default_phone_number,
|
|
72
84
|
default_contact_name: default_contact_name,
|
|
73
85
|
default_config_key: default_config_key,
|
|
74
|
-
configurations:
|
|
86
|
+
configurations: configurations
|
|
75
87
|
}
|
|
76
88
|
end
|
|
77
89
|
|