flow_chat 0.8.0 → 0.8.2

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/docs/configuration.md +2 -2
  3. data/docs/http-gateway-protocol.md +432 -0
  4. data/docs/sessions.md +7 -7
  5. data/docs/ussd-setup.md +1 -1
  6. data/examples/http_controller.rb +154 -0
  7. data/examples/simulator_controller.rb +21 -1
  8. data/examples/ussd_controller.rb +1 -1
  9. data/lib/flow_chat/base_app.rb +86 -0
  10. data/lib/flow_chat/base_executor.rb +57 -0
  11. data/lib/flow_chat/base_processor.rb +7 -6
  12. data/lib/flow_chat/config.rb +17 -2
  13. data/lib/flow_chat/http/app.rb +6 -0
  14. data/lib/flow_chat/http/gateway/simple.rb +77 -0
  15. data/lib/flow_chat/http/middleware/executor.rb +24 -0
  16. data/lib/flow_chat/http/processor.rb +33 -0
  17. data/lib/flow_chat/http/renderer.rb +41 -0
  18. data/lib/flow_chat/instrumentation/setup.rb +0 -2
  19. data/lib/flow_chat/instrumentation.rb +2 -0
  20. data/lib/flow_chat/interrupt.rb +6 -0
  21. data/lib/flow_chat/phone_number_util.rb +47 -0
  22. data/lib/flow_chat/session/cache_session_store.rb +1 -17
  23. data/lib/flow_chat/session/middleware.rb +19 -18
  24. data/lib/flow_chat/simulator/controller.rb +17 -5
  25. data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
  26. data/lib/flow_chat/ussd/app.rb +1 -53
  27. data/lib/flow_chat/ussd/gateway/nalo.rb +3 -7
  28. data/lib/flow_chat/ussd/gateway/nsano.rb +0 -2
  29. data/lib/flow_chat/ussd/middleware/executor.rb +11 -37
  30. data/lib/flow_chat/version.rb +1 -1
  31. data/lib/flow_chat/whatsapp/app.rb +11 -46
  32. data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +16 -14
  33. data/lib/flow_chat/whatsapp/middleware/executor.rb +11 -39
  34. data/lib/flow_chat.rb +1 -11
  35. metadata +12 -2
@@ -31,6 +31,15 @@ class SimulatorController < ApplicationController
31
31
  icon: "💬",
32
32
  color: "#25D366"
33
33
  },
34
+ http_main: {
35
+ name: "Main HTTP API",
36
+ description: "JSON HTTP API endpoint",
37
+ processor_type: "http",
38
+ gateway: "http_simple",
39
+ endpoint: "/http/webhook",
40
+ icon: "🌐",
41
+ color: "#0066cc"
42
+ },
34
43
  whatsapp_tenant_a: {
35
44
  name: "Tenant A WhatsApp",
36
45
  description: "Multi-tenant endpoint for Tenant A",
@@ -40,6 +49,15 @@ class SimulatorController < ApplicationController
40
49
  icon: "🏢",
41
50
  color: "#fd7e14"
42
51
  },
52
+ http_external: {
53
+ name: "External HTTP Test",
54
+ description: "Test with external HTTP server",
55
+ processor_type: "http",
56
+ gateway: "http_simple",
57
+ endpoint: "http://localhost:4567/http/webhook",
58
+ icon: "🔗",
59
+ color: "#17a2b8"
60
+ },
43
61
  whatsapp_legacy: {
44
62
  name: "Legacy WhatsApp",
45
63
  description: "Legacy endpoint for compatibility",
@@ -54,7 +72,7 @@ class SimulatorController < ApplicationController
54
72
 
55
73
  # Default configuration to start with
56
74
  def default_config_key
57
- :whatsapp_main
75
+ :http_main
58
76
  end
59
77
 
60
78
  # Default test phone number
@@ -79,8 +97,10 @@ end
79
97
  # 5. View request/response logs in real-time
80
98
 
81
99
  # This allows you to test:
100
+ # - Different protocol types (USSD, WhatsApp, HTTP)
82
101
  # - Different controller implementations on the same server
83
102
  # - Different API versions (v1, v2, etc.)
84
103
  # - Multi-tenant endpoints with different configurations
85
104
  # - Legacy endpoints alongside new ones
105
+ # - External HTTP servers (run examples/http_simulator_test.rb)
86
106
  # - Different flow implementations for different endpoints
@@ -237,7 +237,7 @@ class UssdController < ApplicationController
237
237
  # Or configure session boundaries explicitly:
238
238
  # config.use_session_config(
239
239
  # boundaries: [:flow, :platform], # which boundaries to enforce
240
- # hash_phone_numbers: true # hash phone numbers for privacy
240
+ # hash_identifiers: true # hash phone numbers for privacy
241
241
  # )
242
242
  end
243
243
 
@@ -0,0 +1,86 @@
1
+ module FlowChat
2
+ class BaseApp
3
+ attr_reader :session, :input, :context, :navigation_stack
4
+
5
+ def initialize(context)
6
+ @context = context
7
+ @session = context.session
8
+ @input = context.input
9
+ @navigation_stack = []
10
+ end
11
+
12
+ def screen(key)
13
+ raise ArgumentError, "a block is expected" unless block_given?
14
+ raise ArgumentError, "screen has already been presented" if navigation_stack.include?(key)
15
+
16
+ navigation_stack << key
17
+ return session.get(key) if session.get(key).present?
18
+
19
+ user_input = prepare_user_input
20
+ prompt = FlowChat::Prompt.new user_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 go_back
29
+ return false if navigation_stack.empty?
30
+
31
+ @context.input = nil
32
+ current_screen = navigation_stack.last
33
+ session.delete(current_screen)
34
+
35
+ # Restart the flow from the beginning
36
+ raise FlowChat::Interrupt::RestartFlow.new
37
+ end
38
+
39
+ def say(msg, media: nil)
40
+ raise FlowChat::Interrupt::Terminate.new(msg, media: media)
41
+ end
42
+
43
+ def platform
44
+ context["request.platform"]
45
+ end
46
+
47
+ def gateway
48
+ context["request.gateway"]
49
+ end
50
+
51
+ def user_id
52
+ context["request.user_id"]
53
+ end
54
+
55
+ def msisdn
56
+ context["request.msisdn"]
57
+ end
58
+
59
+ def message_id
60
+ context["request.message_id"]
61
+ end
62
+
63
+ def timestamp
64
+ context["request.timestamp"]
65
+ end
66
+
67
+ def contact_name
68
+ nil
69
+ end
70
+
71
+ def location
72
+ nil
73
+ end
74
+
75
+ def media
76
+ nil
77
+ end
78
+
79
+ protected
80
+
81
+ # Platform-specific methods to be overridden
82
+ def prepare_user_input
83
+ input
84
+ end
85
+ end
86
+ end
@@ -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
+ [:terminal, 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
@@ -36,14 +36,14 @@ module FlowChat
36
36
  self
37
37
  end
38
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}" }
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
41
 
42
42
  # Update the session options directly
43
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
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
47
 
48
48
  self
49
49
  end
@@ -70,7 +70,7 @@ module FlowChat
70
70
  use_session_config(boundaries: current_boundaries)
71
71
  end
72
72
 
73
- def run(flow_class, action)
73
+ def run(flow_class, action, **options)
74
74
  # Instrument flow execution (this will log via LogSubscriber)
75
75
  instrument(Events::FLOW_EXECUTION_START, {
76
76
  flow_name: flow_class.name.underscore,
@@ -81,6 +81,7 @@ module FlowChat
81
81
  @context["flow.name"] = flow_class.name.underscore
82
82
  @context["flow.class"] = flow_class
83
83
  @context["flow.action"] = action
84
+ @context["flow.options"] = options
84
85
 
85
86
  FlowChat.logger.debug { "BaseProcessor: Context prepared for flow #{flow_class.name}" }
86
87
 
@@ -23,8 +23,13 @@ module FlowChat
23
23
  @whatsapp ||= WhatsappConfig.new
24
24
  end
25
25
 
26
+ # HTTP-specific configuration object
27
+ def self.http
28
+ @http ||= HttpConfig.new
29
+ end
30
+
26
31
  class SessionConfig
27
- attr_accessor :boundaries, :hash_phone_numbers, :identifier
32
+ attr_accessor :boundaries, :hash_identifiers, :identifier
28
33
 
29
34
  def initialize
30
35
  # Session boundaries control how session IDs are constructed
@@ -34,7 +39,7 @@ module FlowChat
34
39
  @boundaries = [:flow, :gateway, :platform]
35
40
 
36
41
  # Always hash phone numbers for privacy
37
- @hash_phone_numbers = true
42
+ @hash_identifiers = true
38
43
 
39
44
  # Session identifier type (nil = let platforms choose their default)
40
45
  # :msisdn = durable sessions (durable across timeouts)
@@ -88,6 +93,16 @@ module FlowChat
88
93
  @message_handling_mode == :simulator
89
94
  end
90
95
  end
96
+
97
+ class HttpConfig
98
+ attr_accessor :default_gateway, :request_timeout, :response_format
99
+
100
+ def initialize
101
+ @default_gateway = :simple
102
+ @request_timeout = 30
103
+ @response_format = :json
104
+ end
105
+ end
91
106
  end
92
107
 
93
108
  # Shorthand for accessing the logger throughout the application
@@ -0,0 +1,6 @@
1
+ module FlowChat
2
+ module Http
3
+ class App < FlowChat::BaseApp
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,77 @@
1
+ module FlowChat
2
+ module Http
3
+ module Gateway
4
+ class Simple
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
+ params = context.controller.request.params
16
+ request = context.controller.request
17
+
18
+ # Extract basic request information
19
+ context["request.id"] = params["session_id"] || SecureRandom.uuid
20
+ context["request.msisdn"] = FlowChat::PhoneNumberUtil.to_e164(params["msisdn"])
21
+ context["request.user_id"] = params["user_id"] || context["request.msisdn"] || context["request.id"]
22
+ context["request.message_id"] = params["message_id"] || SecureRandom.uuid
23
+ context["request.timestamp"] = Time.current.iso8601
24
+ context["request.gateway"] = :http_simple
25
+ context["request.platform"] = :http
26
+ context["request.network"] = nil
27
+ context["request.method"] = request.method
28
+ context["request.path"] = request.path
29
+ context["request.user_agent"] = request.user_agent
30
+ context.input = params["input"] || params["message"]
31
+
32
+ # Instrument message received when user provides input
33
+ if context.input.present?
34
+ instrument(Events::MESSAGE_RECEIVED, {
35
+ from: context["request.user_id"],
36
+ message: context.input,
37
+ timestamp: context["request.timestamp"]
38
+ })
39
+ end
40
+
41
+ # Process the request
42
+ type, prompt, choices, media = @app.call(context)
43
+
44
+ # Instrument message sent
45
+ instrument(Events::MESSAGE_SENT, {
46
+ to: context["request.user_id"],
47
+ session_id: context["request.id"],
48
+ message: context.input || "",
49
+ message_type: (type == :prompt) ? "prompt" : "terminal",
50
+ gateway: :http_simple,
51
+ platform: :http,
52
+ content_length: prompt.to_s.length,
53
+ timestamp: context["request.timestamp"]
54
+ })
55
+
56
+ # Render response as JSON
57
+ response_data = render_response(type, prompt, choices, media)
58
+ context.controller.render json: response_data
59
+ end
60
+
61
+ private
62
+
63
+ def render_response(type, prompt, choices, media)
64
+ rendered = FlowChat::Http::Renderer.new(prompt, choices: choices, media: media).render
65
+
66
+ {
67
+ type: type,
68
+ session_id: context["request.id"],
69
+ user_id: context["request.user_id"],
70
+ timestamp: context["request.timestamp"],
71
+ **rendered
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,24 @@
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
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,41 @@
1
+ module FlowChat
2
+ module Http
3
+ class Renderer
4
+ attr_reader :prompt, :choices, :media
5
+
6
+ def initialize(prompt, choices: nil, media: nil)
7
+ @prompt = prompt
8
+ @choices = choices
9
+ @media = media
10
+ end
11
+
12
+ def render = build_response
13
+
14
+ private
15
+
16
+ def build_response
17
+ {
18
+ message: prompt,
19
+ choices: format_choices,
20
+ media: format_media
21
+ }.compact
22
+ end
23
+
24
+ def format_choices
25
+ return unless choices.present?
26
+
27
+ choices.map { |key, value| { key: key, value: value } }
28
+ end
29
+
30
+ def format_media
31
+ return unless media.present?
32
+
33
+ {
34
+ url: media[:url] || media[:path],
35
+ type: media[:type] || :image,
36
+ caption: media[:caption]
37
+ }.compact
38
+ end
39
+ end
40
+ end
41
+ end
@@ -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
@@ -8,9 +8,11 @@ module FlowChat
8
8
  def instrument(event_name, payload = {}, &block)
9
9
  enriched_payload = payload&.dup || {}
10
10
  if respond_to?(:context) && context
11
+ enriched_payload[:request_id] = context["request.id"] if context["request.id"]
11
12
  enriched_payload[:session_id] = context["session.id"] if context["session.id"]
12
13
  enriched_payload[:flow_name] = context["flow.name"] if context["flow.name"]
13
14
  enriched_payload[:gateway] = context["request.gateway"] if context["request.gateway"]
15
+ enriched_payload[:platform] = context["request.platform"] if context["request.platform"]
14
16
  end
15
17
 
16
18
  self.class.instrument(event_name, enriched_payload, &block)
@@ -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
@@ -0,0 +1,47 @@
1
+ module FlowChat
2
+ module PhoneNumberUtil
3
+ def self.to_e164(phone_number)
4
+ return phone_number if phone_number.nil? || phone_number.empty?
5
+
6
+ begin
7
+ # Try to load phonelib without Rails dependency
8
+ require_phonelib_safely
9
+ Phonelib.parse(phone_number).e164
10
+ rescue => e
11
+ FlowChat.logger.warn { "PhoneNumberUtil: Failed to parse phone number '#{phone_number}': #{e.message}" }
12
+ # Fallback to simple formatting if phonelib fails
13
+ fallback_e164_format(phone_number)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def self.require_phonelib_safely
20
+ return if defined?(Phonelib)
21
+
22
+ # Temporarily stub Rails if it doesn't exist
23
+ unless defined?(Rails)
24
+ stub_rails = Module.new do
25
+ def self.const_missing(name)
26
+ if name == :Railtie
27
+ Class.new
28
+ else
29
+ super
30
+ end
31
+ end
32
+ end
33
+ Object.const_set(:Rails, stub_rails)
34
+ require "phonelib"
35
+ Object.send(:remove_const, :Rails)
36
+ else
37
+ require "phonelib"
38
+ end
39
+ end
40
+
41
+ def self.fallback_e164_format(phone_number)
42
+ # Simple fallback - ensure it starts with + and looks like a phone number
43
+ cleaned = phone_number.to_s.gsub(/[^\d+]/, '')
44
+ cleaned.start_with?('+') ? cleaned : "+#{cleaned}"
45
+ end
46
+ end
47
+ end
@@ -111,23 +111,7 @@ module FlowChat
111
111
  private
112
112
 
113
113
  def session_key
114
- return "flow_chat:session:nil_context" unless @context
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
@@ -64,32 +64,33 @@ module FlowChat
64
64
  end
65
65
 
66
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
67
+ identifier_type = @session_options.identifier || platform_default_identifier(context)
81
68
 
82
69
  case identifier_type
83
70
  when :request_id
84
71
  context["request.id"]
72
+ when :user_id
73
+ user_id = context["request.user_id"]
74
+ @session_options.hash_identifiers ? hash_identifier(user_id) : user_id
85
75
  when :msisdn
86
- phone = context["request.msisdn"]
87
- @session_options.hash_phone_numbers ? hash_phone_number(phone) : phone
76
+ msisdn = context["request.msisdn"]
77
+ @session_options.hash_identifiers ? hash_identifier(msisdn) : msisdn
88
78
  else
89
79
  raise "Invalid session identifier type: #{identifier_type}"
90
80
  end
91
81
  end
92
82
 
83
+ def platform_default_identifier(context)
84
+ platform = context["request.platform"]
85
+
86
+ case platform
87
+ when :whatsapp
88
+ :msisdn
89
+ else
90
+ :request_id
91
+ end
92
+ end
93
+
93
94
  def build_session_id(flow_name, platform, gateway, identifier)
94
95
  parts = []
95
96
 
@@ -142,10 +143,10 @@ module FlowChat
142
143
  url_identifier
143
144
  end
144
145
 
145
- def hash_phone_number(phone)
146
+ def hash_identifier(identifier)
146
147
  # Use SHA256 but only take first 8 characters for reasonable session IDs
147
148
  require 'digest'
148
- Digest::SHA256.hexdigest(phone.to_s)[0, 8]
149
+ Digest::SHA256.hexdigest(identifier.to_s)[0, 8]
149
150
  end
150
151
  end
151
152
  end