flow_chat 0.7.0 → 0.8.1

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.
@@ -0,0 +1,57 @@
1
+ module FlowChat
2
+ class BaseExecutor
3
+ def initialize(app)
4
+ @app = app
5
+ FlowChat.logger.debug { "#{log_prefix}: Initialized #{platform_name} executor middleware" }
6
+ end
7
+
8
+ def call(context)
9
+ flow_class = context.flow
10
+ action = context["flow.action"]
11
+ session_id = context["session.id"]
12
+
13
+ FlowChat.logger.info { "#{log_prefix}: Executing flow #{flow_class.name}##{action} for session #{session_id}" }
14
+
15
+ platform_app = build_platform_app(context)
16
+ FlowChat.logger.debug { "#{log_prefix}: #{platform_name} app built for flow execution" }
17
+
18
+ flow = flow_class.new platform_app
19
+ FlowChat.logger.debug { "#{log_prefix}: Flow instance created, invoking #{action} method" }
20
+
21
+ flow.send action
22
+ FlowChat.logger.warn { "#{log_prefix}: Flow execution failed to interact with user for #{flow_class.name}##{action}" }
23
+ raise FlowChat::Interrupt::Terminate, "Unexpected end of flow."
24
+ rescue FlowChat::Interrupt::RestartFlow => e
25
+ FlowChat.logger.info { "#{log_prefix}: Flow restart requested - Session: #{session_id}, restarting #{action}" }
26
+ retry
27
+ rescue FlowChat::Interrupt::Prompt => e
28
+ FlowChat.logger.info { "#{log_prefix}: Flow prompted user - Session: #{session_id}, Prompt: '#{e.prompt&.truncate(100)}'" }
29
+ FlowChat.logger.debug { "#{log_prefix}: Prompt details - Choices: #{e.choices&.size || 0}, Has media: #{!e.media.nil?}" }
30
+ [:prompt, e.prompt, e.choices, e.media]
31
+ rescue FlowChat::Interrupt::Terminate => e
32
+ FlowChat.logger.info { "#{log_prefix}: Flow terminated - Session: #{session_id}, Message: '#{e.prompt&.truncate(100)}'" }
33
+ FlowChat.logger.debug { "#{log_prefix}: Destroying session #{session_id}" }
34
+ context.session.destroy
35
+ [:terminate, e.prompt, nil, e.media]
36
+ rescue => error
37
+ FlowChat.logger.error { "#{log_prefix}: Flow execution failed - #{flow_class.name}##{action}, Session: #{session_id}, Error: #{error.class.name}: #{error.message}" }
38
+ FlowChat.logger.debug { "#{log_prefix}: Stack trace: #{error.backtrace.join("\n")}" }
39
+ raise
40
+ end
41
+
42
+ protected
43
+
44
+ # Subclasses must implement these methods
45
+ def platform_name
46
+ raise NotImplementedError, "Subclasses must implement platform_name"
47
+ end
48
+
49
+ def log_prefix
50
+ raise NotImplementedError, "Subclasses must implement log_prefix"
51
+ end
52
+
53
+ def build_platform_app(context)
54
+ raise NotImplementedError, "Subclasses must implement build_platform_app"
55
+ end
56
+ end
57
+ end
@@ -13,6 +13,7 @@ module FlowChat
13
13
  @context["controller"] = controller
14
14
  @context["enable_simulator"] = enable_simulator.nil? ? (defined?(Rails) && Rails.env.local?) : enable_simulator
15
15
  @middleware = ::Middleware::Builder.new(name: middleware_name)
16
+ @session_options = FlowChat::Config.session
16
17
 
17
18
  FlowChat.logger.debug { "BaseProcessor: Simulator mode #{@context["enable_simulator"] ? "enabled" : "disabled"}" }
18
19
 
@@ -29,17 +30,46 @@ module FlowChat
29
30
  end
30
31
 
31
32
  def use_session_store(session_store)
32
- FlowChat.logger.debug { "BaseProcessor: Configuring session store #{session_store.class.name}" }
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}" }
33
35
  @context["session.store"] = session_store
34
36
  self
35
37
  end
36
38
 
39
+ def use_session_config(boundaries: nil, hash_phone_numbers: nil, identifier: nil)
40
+ FlowChat.logger.debug { "BaseProcessor: Configuring session config: boundaries=#{boundaries.inspect}, hash_phone_numbers=#{hash_phone_numbers}, identifier=#{identifier}" }
41
+
42
+ # Update the session options directly
43
+ @session_options = @session_options.dup
44
+ @session_options.boundaries = Array(boundaries) if boundaries
45
+ @session_options.hash_phone_numbers = hash_phone_numbers if hash_phone_numbers
46
+ @session_options.identifier = identifier if identifier
47
+
48
+ self
49
+ end
50
+
37
51
  def use_middleware(middleware)
38
- FlowChat.logger.debug { "BaseProcessor: Adding middleware #{middleware.class.name}" }
52
+ raise "Middleware must be a class" unless middleware.is_a?(Class)
53
+ FlowChat.logger.debug { "BaseProcessor: Adding middleware #{middleware.name}" }
39
54
  @middleware.use middleware
40
55
  self
41
56
  end
42
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
+
43
73
  def run(flow_class, action)
44
74
  # Instrument flow execution (this will log via LogSubscriber)
45
75
  instrument(Events::FLOW_EXECUTION_START, {
@@ -101,6 +131,7 @@ module FlowChat
101
131
 
102
132
  ::Middleware::Builder.new(name: name) do |b|
103
133
  b.use @gateway_class, *@gateway_args
134
+ b.use FlowChat::Session::Middleware, @session_options
104
135
  configure_middleware_stack(b)
105
136
  end.inject_logger(FlowChat.logger)
106
137
  end
@@ -8,6 +8,11 @@ module FlowChat
8
8
  # When false, only the validation error message is shown to the user.
9
9
  mattr_accessor :combine_validation_error_with_message, default: true
10
10
 
11
+ # Session configuration object
12
+ def self.session
13
+ @session ||= SessionConfig.new
14
+ end
15
+
11
16
  # USSD-specific configuration object
12
17
  def self.ussd
13
18
  @ussd ||= UssdConfig.new
@@ -18,10 +23,29 @@ module FlowChat
18
23
  @whatsapp ||= WhatsappConfig.new
19
24
  end
20
25
 
26
+ class SessionConfig
27
+ attr_accessor :boundaries, :hash_phone_numbers, :identifier
28
+
29
+ def initialize
30
+ # Session boundaries control how session IDs are constructed
31
+ # :flow = separate sessions per flow
32
+ # :gateway = separate sessions per gateway
33
+ # :platform = separate sessions per platform (ussd, whatsapp)
34
+ @boundaries = [:flow, :gateway, :platform]
35
+
36
+ # Always hash phone numbers for privacy
37
+ @hash_phone_numbers = true
38
+
39
+ # Session identifier type (nil = let platforms choose their default)
40
+ # :msisdn = durable sessions (durable across timeouts)
41
+ # :request_id = ephemeral sessions (new session each time)
42
+ @identifier = nil
43
+ end
44
+ end
45
+
21
46
  class UssdConfig
22
47
  attr_accessor :pagination_page_size, :pagination_back_option, :pagination_back_text,
23
- :pagination_next_option, :pagination_next_text,
24
- :resumable_sessions_enabled, :resumable_sessions_global, :resumable_sessions_timeout_seconds
48
+ :pagination_next_option, :pagination_next_text
25
49
 
26
50
  def initialize
27
51
  @pagination_page_size = 140
@@ -29,9 +53,6 @@ module FlowChat
29
53
  @pagination_back_text = "Back"
30
54
  @pagination_next_option = "#"
31
55
  @pagination_next_text = "More"
32
- @resumable_sessions_enabled = false
33
- @resumable_sessions_global = true
34
- @resumable_sessions_timeout_seconds = 300
35
56
  end
36
57
  end
37
58
 
@@ -22,7 +22,6 @@ module FlowChat
22
22
  def setup_logging!(options = {})
23
23
  return if @log_subscriber_setup
24
24
 
25
- require_relative "log_subscriber"
26
25
  setup_log_subscriber(options)
27
26
  @log_subscriber_setup = true
28
27
  end
@@ -31,7 +30,6 @@ module FlowChat
31
30
  def setup_metrics!(options = {})
32
31
  return if @metrics_collector_setup
33
32
 
34
- require_relative "metrics_collector"
35
33
  setup_metrics_collector(options)
36
34
  @metrics_collector_setup = true
37
35
  end
@@ -22,5 +22,11 @@ module FlowChat
22
22
  end
23
23
 
24
24
  class Terminate < Base; end
25
+
26
+ class RestartFlow < Base
27
+ def initialize
28
+ super("restart_flow")
29
+ end
30
+ end
25
31
  end
26
32
  end
@@ -5,8 +5,9 @@ module FlowChat
5
5
 
6
6
  attr_reader :context
7
7
 
8
- def initialize(app)
8
+ def initialize(app, session_options)
9
9
  @app = app
10
+ @session_options = session_options
10
11
  FlowChat.logger.debug { "Session::Middleware: Initialized session middleware" }
11
12
  end
12
13
 
@@ -19,9 +20,10 @@ module FlowChat
19
20
  context.session = context["session.store"].new(context)
20
21
 
21
22
  # Use instrumentation instead of direct logging for session creation
23
+ store_type = context["session.store"].name || "$Anonymous"
22
24
  instrument(Events::SESSION_CREATED, {
23
25
  session_id: session_id,
24
- store_type: context["session.store"].name,
26
+ store_type: store_type,
25
27
  gateway: context["request.gateway"]
26
28
  })
27
29
 
@@ -40,28 +42,111 @@ module FlowChat
40
42
 
41
43
  def session_id(context)
42
44
  gateway = context["request.gateway"]
45
+ platform = context["request.platform"]
43
46
  flow_name = context["flow.name"]
44
47
 
45
- FlowChat.logger.debug { "Session::Middleware: Building session ID for gateway=#{gateway}, flow=#{flow_name}" }
48
+ # Check for explicit session ID first (for manual session management)
49
+ if context["session.id"].present?
50
+ session_id = context["session.id"]
51
+ FlowChat.logger.debug { "Session::Middleware: Using explicit session ID: #{session_id}" }
52
+ return session_id
53
+ end
54
+
55
+ FlowChat.logger.debug { "Session::Middleware: Building session ID for platform=#{platform}, gateway=#{gateway}, flow=#{flow_name}" }
46
56
 
47
- case gateway
48
- when :whatsapp_cloud_api
49
- # For WhatsApp, use phone number + flow name for consistent sessions
57
+ # Get identifier based on configuration
58
+ identifier = get_session_identifier(context)
59
+
60
+ # Build session ID based on configuration
61
+ session_id = build_session_id(flow_name, platform, gateway, identifier)
62
+ FlowChat.logger.debug { "Session::Middleware: Generated session ID: #{session_id}" }
63
+ session_id
64
+ end
65
+
66
+ 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
+
82
+ case identifier_type
83
+ when :request_id
84
+ context["request.id"]
85
+ when :msisdn
50
86
  phone = context["request.msisdn"]
51
- session_id = "#{gateway}:#{flow_name}:#{phone}"
52
- FlowChat.logger.debug { "Session::Middleware: WhatsApp session ID created for phone #{phone}" }
53
- session_id
54
- # when :nalo, :nsano
55
- # # For USSD, use the request ID from the gateway
56
- # "#{gateway}:#{flow_name}:#{context["request.id"]}"
87
+ @session_options.hash_phone_numbers ? hash_phone_number(phone) : phone
57
88
  else
58
- # Fallback to request ID
59
- request_id = context["request.id"]
60
- session_id = "#{gateway}:#{flow_name}:#{request_id}"
61
- FlowChat.logger.debug { "Session::Middleware: Generic session ID created for request #{request_id}" }
62
- session_id
89
+ raise "Invalid session identifier type: #{identifier_type}"
63
90
  end
64
91
  end
92
+
93
+ def build_session_id(flow_name, platform, gateway, identifier)
94
+ parts = []
95
+
96
+ # Add flow name if flow isolation is enabled
97
+ parts << flow_name if @session_options.boundaries.include?(:flow)
98
+
99
+ # Add platform if platform isolation is enabled
100
+ parts << platform.to_s if @session_options.boundaries.include?(:platform)
101
+
102
+ # Add gateway if gateway isolation is enabled
103
+ parts << gateway.to_s if @session_options.boundaries.include?(:gateway)
104
+
105
+ # Add URL if URL isolation is enabled
106
+ if @session_options.boundaries.include?(:url)
107
+ url_identifier = get_url_identifier(context)
108
+ parts << url_identifier if url_identifier.present?
109
+ end
110
+
111
+ # Add the session identifier
112
+ parts << identifier if identifier.present?
113
+
114
+ # Join parts with colons
115
+ parts.join(":")
116
+ end
117
+
118
+ def get_url_identifier(context)
119
+ request = context.controller&.request
120
+ return nil unless request
121
+
122
+ # Extract host and path for URL boundary
123
+ host = request.host rescue nil
124
+ path = request.path rescue nil
125
+
126
+ # Create a normalized URL identifier: host + path
127
+ # e.g., "example.com/api/v1/ussd" or "tenant1.example.com/ussd"
128
+ url_parts = []
129
+ url_parts << host if host.present?
130
+ url_parts << path.sub(/^\//, '') if path.present? && path != '/'
131
+
132
+ # For long URLs, use first part + hash suffix instead of full hash
133
+ url_identifier = url_parts.join('/').gsub(/[^a-zA-Z0-9._-]/, '_')
134
+ if url_identifier.length > 50
135
+ require 'digest'
136
+ # Take first 41 chars + hash suffix to keep it manageable but recognizable
137
+ first_part = url_identifier[0, 41]
138
+ hash_suffix = Digest::SHA256.hexdigest(url_identifier)[0, 8]
139
+ url_identifier = "#{first_part}_#{hash_suffix}"
140
+ end
141
+
142
+ url_identifier
143
+ end
144
+
145
+ def hash_phone_number(phone)
146
+ # Use SHA256 but only take first 8 characters for reasonable session IDs
147
+ require 'digest'
148
+ Digest::SHA256.hexdigest(phone.to_s)[0, 8]
149
+ end
65
150
  end
66
151
  end
67
152
  end
@@ -32,7 +32,7 @@ module FlowChat
32
32
  name: "USSD (Nalo)",
33
33
  description: "USSD integration using Nalo",
34
34
  processor_type: "ussd",
35
- provider: "nalo",
35
+ gateway: "nalo",
36
36
  endpoint: "/ussd",
37
37
  icon: "📱",
38
38
  color: "#28a745",
@@ -45,7 +45,7 @@ module FlowChat
45
45
  name: "WhatsApp (Cloud API)",
46
46
  description: "WhatsApp integration using Cloud API",
47
47
  processor_type: "whatsapp",
48
- provider: "cloud_api",
48
+ gateway: "cloud_api",
49
49
  endpoint: "/whatsapp/webhook",
50
50
  icon: "💬",
51
51
  color: "#25D366",
@@ -1057,8 +1057,8 @@
1057
1057
  <span class="config-detail-value">${config.endpoint}</span>
1058
1058
  </div>
1059
1059
  <div class="config-detail-item">
1060
- <span class="config-detail-label">Provider:</span>
1061
- <span class="config-detail-value">${config.provider}</span>
1060
+ <span class="config-detail-label">gateway:</span>
1061
+ <span class="config-detail-value">${config.gateway}</span>
1062
1062
  </div>
1063
1063
  <div class="config-detail-item">
1064
1064
  <span class="config-detail-label">Type:</span>
@@ -1304,7 +1304,7 @@
1304
1304
 
1305
1305
  let requestData = {}
1306
1306
 
1307
- switch (config.provider) {
1307
+ switch (config.gateway) {
1308
1308
  case 'nalo':
1309
1309
  requestData = {
1310
1310
  USERID: state.sessionId,
@@ -1322,7 +1322,7 @@
1322
1322
  }
1323
1323
  break
1324
1324
  default:
1325
- throw new Error(`Unsupported USSD provider: ${config.provider}`)
1325
+ throw new Error(`Unsupported USSD gateway: ${config.gateway}`)
1326
1326
  }
1327
1327
 
1328
1328
  try {
@@ -1342,7 +1342,7 @@
1342
1342
  throw new Error(`HTTP ${response.status}: ${response.statusText}`)
1343
1343
  }
1344
1344
 
1345
- switch (config.provider) {
1345
+ switch (config.gateway) {
1346
1346
  case 'nalo':
1347
1347
  displayUSSDResponse(data.MSG)
1348
1348
  state.isRunning = data.MSGTYPE
@@ -1,58 +1,6 @@
1
1
  module FlowChat
2
2
  module Ussd
3
- class App
4
- attr_reader :session, :input, :context, :navigation_stack
5
-
6
- def initialize(context)
7
- @context = context
8
- @session = context.session
9
- @input = context.input
10
- @navigation_stack = []
11
- end
12
-
13
- def screen(key)
14
- raise ArgumentError, "a block is expected" unless block_given?
15
- raise ArgumentError, "screen has already been presented" if navigation_stack.include?(key)
16
-
17
- navigation_stack << key
18
- return session.get(key) if session.get(key).present?
19
-
20
- prompt = FlowChat::Prompt.new input
21
- @input = nil # input is being submitted to prompt so we clear it
22
-
23
- value = yield prompt
24
- session.set(key, value)
25
- value
26
- end
27
-
28
- def say(msg, media: nil)
29
- raise FlowChat::Interrupt::Terminate.new(msg)
30
- end
31
-
32
- # WhatsApp-specific data accessors (not supported in USSD)
33
- def contact_name
34
- nil
35
- end
36
-
37
- def message_id
38
- context["request.message_id"]
39
- end
40
-
41
- def timestamp
42
- context["request.timestamp"]
43
- end
44
-
45
- def location
46
- nil
47
- end
48
-
49
- def media
50
- nil
51
- end
52
-
53
- def phone_number
54
- context["request.msisdn"]
55
- end
3
+ class App < FlowChat::BaseApp
56
4
  end
57
5
  end
58
6
  end
@@ -20,6 +20,7 @@ module FlowChat
20
20
  context["request.message_id"] = SecureRandom.uuid
21
21
  context["request.timestamp"] = Time.current.iso8601
22
22
  context["request.gateway"] = :nalo
23
+ context["request.platform"] = :ussd
23
24
  context["request.network"] = nil
24
25
  context["request.msisdn"] = Phonelib.parse(params["MSISDN"]).e164
25
26
  # context["request.type"] = params["MSGTYPE"] ? :initial : :response
@@ -22,6 +22,7 @@ module FlowChat
22
22
 
23
23
  # Set a basic message_id (can be enhanced based on actual Nsano implementation)
24
24
  context["request.message_id"] = SecureRandom.uuid
25
+ context["request.platform"] = :ussd
25
26
 
26
27
  # TODO: Implement Nsano-specific parameter parsing
27
28
  # For now, add basic instrumentation structure for when this is implemented
@@ -58,7 +59,7 @@ module FlowChat
58
59
  # if params["network"].present? && params["UserSessionID"].present?
59
60
  # request_id = "nsano::request_id::#{params["UserSessionID"]}"
60
61
  # context["ussd.request"] = {
61
- # provider: :nsano,
62
+ # gateway: :nsano,
62
63
  # network: params["network"].to_sym,
63
64
  # msisdn: Phonelib.parse(params["msisdn"]).e164,
64
65
  # type: Config.cache&.read(request_id).present? ? :response : :initial,
@@ -70,7 +71,7 @@ module FlowChat
70
71
 
71
72
  # status, headers, response = @app.call(context)
72
73
 
73
- # if context["ussd.response"].present? && context["ussd.request"][:provider] == :nsano
74
+ # if context["ussd.response"].present? && context["ussd.request"][:gateway] == :nsano
74
75
  # if context["ussd.response"][:type] == :terminal
75
76
  # Config.cache&.write(request_id, nil)
76
77
  # else
@@ -1,47 +1,21 @@
1
+ require_relative "../../base_executor"
2
+
1
3
  module FlowChat
2
4
  module Ussd
3
5
  module Middleware
4
- class Executor
5
- def initialize(app)
6
- @app = app
7
- FlowChat.logger.debug { "Ussd::Executor: Initialized USSD executor middleware" }
8
- end
9
-
10
- def call(context)
11
- flow_class = context.flow
12
- action = context["flow.action"]
13
- session_id = context["session.id"]
14
-
15
- FlowChat.logger.info { "Ussd::Executor: Executing flow #{flow_class.name}##{action} for session #{session_id}" }
6
+ class Executor < FlowChat::BaseExecutor
7
+ protected
16
8
 
17
- ussd_app = build_ussd_app context
18
- FlowChat.logger.debug { "Ussd::Executor: USSD app built for flow execution" }
19
-
20
- flow = flow_class.new ussd_app
21
- FlowChat.logger.debug { "Ussd::Executor: Flow instance created, invoking #{action} method" }
22
-
23
- flow.send action
24
- FlowChat.logger.warn { "Ussd::Executor: Flow execution failed to interact with user for #{flow_class.name}##{action}" }
25
- raise FlowChat::Interrupt::Terminate, "Unexpected end of flow."
26
- rescue FlowChat::Interrupt::Prompt => e
27
- FlowChat.logger.info { "Ussd::Executor: Flow prompted user - Session: #{session_id}, Prompt: '#{e.prompt.truncate(100)}'" }
28
- FlowChat.logger.debug { "Ussd::Executor: Prompt details - Choices: #{e.choices&.size || 0}, Has media: #{!e.media.nil?}" }
29
- [:prompt, e.prompt, e.choices, e.media]
30
- rescue FlowChat::Interrupt::Terminate => e
31
- FlowChat.logger.info { "Ussd::Executor: Flow terminated - Session: #{session_id}, Message: '#{e.prompt.truncate(100)}'" }
32
- FlowChat.logger.debug { "Ussd::Executor: Destroying session #{session_id}" }
33
- context.session.destroy
34
- [:terminate, e.prompt, nil, e.media]
35
- rescue => error
36
- FlowChat.logger.error { "Ussd::Executor: Flow execution failed - #{flow_class.name}##{action}, Session: #{session_id}, Error: #{error.class.name}: #{error.message}" }
37
- FlowChat.logger.debug { "Ussd::Executor: Stack trace: #{error.backtrace.join("\n")}" }
38
- raise
9
+ def platform_name
10
+ "USSD"
39
11
  end
40
12
 
41
- private
13
+ def log_prefix
14
+ "Ussd::Executor"
15
+ end
42
16
 
43
- def build_ussd_app(context)
44
- FlowChat.logger.debug { "Ussd::Executor: Building USSD app instance" }
17
+ def build_platform_app(context)
18
+ FlowChat.logger.debug { "#{log_prefix}: Building USSD app instance" }
45
19
  FlowChat::Ussd::App.new(context)
46
20
  end
47
21
  end
@@ -1,10 +1,11 @@
1
1
  module FlowChat
2
2
  module Ussd
3
3
  class Processor < FlowChat::BaseProcessor
4
- def use_resumable_sessions
5
- FlowChat.logger.debug { "Ussd::Processor: Enabling resumable sessions middleware" }
6
- middleware.insert_before 0, FlowChat::Ussd::Middleware::ResumableSession
7
- self
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
+ )
8
9
  end
9
10
 
10
11
  protected
@@ -21,9 +22,6 @@ module FlowChat
21
22
  def configure_middleware_stack(builder)
22
23
  FlowChat.logger.debug { "Ussd::Processor: Configuring USSD middleware stack" }
23
24
 
24
- builder.use FlowChat::Session::Middleware
25
- FlowChat.logger.debug { "Ussd::Processor: Added Session::Middleware" }
26
-
27
25
  builder.use FlowChat::Ussd::Middleware::Pagination
28
26
  FlowChat.logger.debug { "Ussd::Processor: Added Ussd::Middleware::Pagination" }
29
27
 
@@ -1,3 +1,3 @@
1
1
  module FlowChat
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.1"
3
3
  end
@@ -1,53 +1,10 @@
1
1
  module FlowChat
2
2
  module Whatsapp
3
- class App
4
- attr_reader :session, :input, :context, :navigation_stack
5
-
6
- def initialize(context)
7
- @context = context
8
- @session = context.session
9
- @input = context.input
10
- @navigation_stack = []
11
- end
12
-
13
- def screen(key)
14
- raise ArgumentError, "a block is expected" unless block_given?
15
- raise ArgumentError, "screen has already been presented" if navigation_stack.include?(key)
16
-
17
- navigation_stack << key
18
- return session.get(key) if session.get(key).present?
19
-
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
-
26
- prompt = FlowChat::Prompt.new user_input
27
- @input = nil # input is being submitted to prompt so we clear it
28
-
29
- value = yield prompt
30
- session.set(key, value)
31
- value
32
- end
33
-
34
- def say(msg, media: nil)
35
- raise FlowChat::Interrupt::Terminate.new(msg, media: media)
36
- end
37
-
38
- # WhatsApp-specific data accessors (read-only)
3
+ class App < FlowChat::BaseApp
39
4
  def contact_name
40
5
  context["request.contact_name"]
41
6
  end
42
7
 
43
- def message_id
44
- context["request.message_id"]
45
- end
46
-
47
- def timestamp
48
- context["request.timestamp"]
49
- end
50
-
51
8
  def location
52
9
  context["request.location"]
53
10
  end
@@ -56,8 +13,16 @@ module FlowChat
56
13
  context["request.media"]
57
14
  end
58
15
 
59
- def phone_number
60
- context["request.msisdn"]
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
61
26
  end
62
27
  end
63
28
  end