colloquy 1.0.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 +15 -0
- data/README.md +160 -0
- data/TODO.md +256 -0
- data/bin/colloquy +14 -0
- data/examples/config/flows.yaml +22 -0
- data/examples/config/logger.yaml +2 -0
- data/examples/config/messages.yaml +3 -0
- data/examples/config/mysql.yaml +18 -0
- data/examples/config/redis.yaml +9 -0
- data/examples/config/scribe.yaml +2 -0
- data/examples/config/settings.yaml +2 -0
- data/examples/config/test.yaml +8 -0
- data/examples/config/urls.yaml +18 -0
- data/examples/flows/active_record_flow.rb +42 -0
- data/examples/flows/art_of_war_flow.rb +27 -0
- data/examples/flows/calculator_flow.rb +71 -0
- data/examples/flows/crossover_flow.rb +17 -0
- data/examples/flows/database_flow.rb +33 -0
- data/examples/flows/hangman_flow.rb +82 -0
- data/examples/flows/metadata_flow.rb +11 -0
- data/examples/flows/pagination_flow.rb +23 -0
- data/examples/flows/pass_flow.rb +29 -0
- data/examples/flows/prefix_menu_flow.rb +24 -0
- data/examples/flows/scribe_flow.rb +26 -0
- data/examples/flows/settings_flow.rb +23 -0
- data/examples/flows/special/special_redis_flow.rb +28 -0
- data/examples/flows/url_flow.rb +27 -0
- data/examples/log/renderer.log +198381 -0
- data/examples/log/urls.log +3269 -0
- data/examples/messages/active_record.yaml +2 -0
- data/examples/messages/art_of_war.yaml +1 -0
- data/examples/messages/calculator.yaml +2 -0
- data/examples/messages/database.yaml +1 -0
- data/examples/messages/hangman.yaml +0 -0
- data/examples/messages/prefix_menu.yaml +3 -0
- data/examples/models/activations.rb +5 -0
- data/lib/colloquy.rb +39 -0
- data/lib/colloquy/exceptions.rb +64 -0
- data/lib/colloquy/flow_parser.rb +315 -0
- data/lib/colloquy/flow_pool.rb +21 -0
- data/lib/colloquy/helpers.rb +15 -0
- data/lib/colloquy/helpers/mysql.rb +110 -0
- data/lib/colloquy/helpers/redis.rb +103 -0
- data/lib/colloquy/helpers/scribe.rb +103 -0
- data/lib/colloquy/helpers/settings.rb +111 -0
- data/lib/colloquy/helpers/url.rb +10 -0
- data/lib/colloquy/input.rb +14 -0
- data/lib/colloquy/logger.rb +11 -0
- data/lib/colloquy/menu.rb +128 -0
- data/lib/colloquy/message_builder.rb +54 -0
- data/lib/colloquy/node.rb +67 -0
- data/lib/colloquy/paginator.rb +59 -0
- data/lib/colloquy/paginator/menu.rb +79 -0
- data/lib/colloquy/paginator/prompt.rb +32 -0
- data/lib/colloquy/prompt.rb +35 -0
- data/lib/colloquy/renderer.rb +423 -0
- data/lib/colloquy/response.rb +4 -0
- data/lib/colloquy/runner.rb +93 -0
- data/lib/colloquy/server.rb +157 -0
- data/lib/colloquy/session_store.rb +46 -0
- data/lib/colloquy/session_store/memory.rb +14 -0
- data/lib/colloquy/session_store/redis.rb +24 -0
- data/lib/colloquy/simulator.rb +114 -0
- data/lib/colloquy/spec_helpers.rb +21 -0
- metadata +459 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
class Colloquy::Prompt
|
3
|
+
include Colloquy::Paginator
|
4
|
+
|
5
|
+
attr_accessor :flow
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@message = options[:message] || ''
|
9
|
+
@page = options[:page] || 1
|
10
|
+
@flow = options[:flow] || nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(page = @page)
|
14
|
+
paginate unless @pages
|
15
|
+
|
16
|
+
execute_before_page_hook
|
17
|
+
"#{render_body(page)}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(string)
|
21
|
+
@message == string
|
22
|
+
end
|
23
|
+
|
24
|
+
def freeze
|
25
|
+
@message.freeze
|
26
|
+
end
|
27
|
+
|
28
|
+
def key(input)
|
29
|
+
if input.to_s == '1'
|
30
|
+
:more
|
31
|
+
else
|
32
|
+
:unknown
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,423 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'active_support/core_ext/hash'
|
4
|
+
require 'colloquy/logger'
|
5
|
+
|
6
|
+
class Colloquy::Renderer
|
7
|
+
DEFAULT_ERROR_MESSAGE = 'This service is not available at present. Please try again later!'
|
8
|
+
|
9
|
+
attr_reader :logger
|
10
|
+
|
11
|
+
# Extracts root path from options hash and creates a HashWithIndifferentAccess object using options.
|
12
|
+
#
|
13
|
+
# @param [Hash] options The options hash
|
14
|
+
def initialize(options = {})
|
15
|
+
Colloquy.root = options[:path_root] if options[:path_root]
|
16
|
+
@options = options.with_indifferent_access
|
17
|
+
end
|
18
|
+
|
19
|
+
# Initializes renderer components if given configuration is valid.
|
20
|
+
# The #configure it executes does lots of initializations and configurations. For details see the method.
|
21
|
+
def prepare!
|
22
|
+
configure if configuration_valid?
|
23
|
+
end
|
24
|
+
|
25
|
+
# This method is the only endpoint of Renderer used by Server#response. It receives flow_name, msisdn, session_id, input and metadata
|
26
|
+
# provided by Server and returns response after doing lots of stuff with the input.
|
27
|
+
#
|
28
|
+
# It makes a session key for each received call, fetches a flow instance corresponding to it (which is an instance
|
29
|
+
# of some class with module FlowParser mixed in), and pass all this to #apply!
|
30
|
+
#
|
31
|
+
# @return [Response] response The processed response
|
32
|
+
def apply(flow_name, msisdn, session_id, input = nil, metadata = {})
|
33
|
+
response = ''
|
34
|
+
flow_state = :notify
|
35
|
+
|
36
|
+
begin
|
37
|
+
session_key = make_session_key(msisdn, session_id)
|
38
|
+
state, session, flow = state_load(flow_name, session_key, metadata)
|
39
|
+
|
40
|
+
response = apply!(flow, state, session, session_key, flow_name, msisdn, session_id, input)
|
41
|
+
flow_state = @state[flow_name.to_sym][session_key][:flow_state].to_sym
|
42
|
+
rescue Exception => e
|
43
|
+
logger.error "Exception: #{e.inspect} in #{e.backtrace[0]} when processing: flow: #{flow_name}, msisdn: #{msisdn}, session_id: #{session_id}, input: #{input}"
|
44
|
+
logger.debug e.backtrace.inspect
|
45
|
+
|
46
|
+
begin
|
47
|
+
logger.debug 'Responding with default error message.'
|
48
|
+
flow_for_messages = @flows[flow_name.to_sym]
|
49
|
+
|
50
|
+
response = Colloquy::MessageBuilder.to_message(:error_unexpected, flow: flow_for_messages)
|
51
|
+
flow_state = :notify
|
52
|
+
rescue Exception => e
|
53
|
+
logger.error 'An error occured when we tried to render the error message, falling back to default error response'
|
54
|
+
logger.error "Exception: #{e.inspect} in #{e.backtrace[0]}"
|
55
|
+
logger.debug e.backtrace.inspect
|
56
|
+
|
57
|
+
response = DEFAULT_ERROR_MESSAGE
|
58
|
+
flow_state = :notify
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# We construct a response object
|
63
|
+
response = Colloquy::Response.new(response)
|
64
|
+
response.flow_state = flow_state
|
65
|
+
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
def reload_messages!
|
70
|
+
@messages = {}
|
71
|
+
|
72
|
+
load_messages
|
73
|
+
end
|
74
|
+
|
75
|
+
def reload_flows!
|
76
|
+
@flows = {}
|
77
|
+
|
78
|
+
load_flows
|
79
|
+
load_messages
|
80
|
+
end
|
81
|
+
|
82
|
+
def reload_flow!(flow_name)
|
83
|
+
@flows[flow_name.to_sym] = nil
|
84
|
+
load_flow(flow_name)
|
85
|
+
load_messages_into_flow(flow_name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def flow_exists?(flow_name)
|
89
|
+
@flows[flow_name.to_sym]
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Checks all configurations including flows, logger and messages.
|
95
|
+
# @return [true] if all are valid, raises an exception if not.
|
96
|
+
def configuration_valid?
|
97
|
+
@options[:path_config] = Colloquy.root.join('config')
|
98
|
+
|
99
|
+
configuration_directory_exists?
|
100
|
+
|
101
|
+
%W(flows logger messages).each do |file|
|
102
|
+
method_name = "#{file}_yaml_exists?".to_sym
|
103
|
+
send(method_name)
|
104
|
+
|
105
|
+
key = "path_#{file}_yaml".to_sym
|
106
|
+
@options[file.to_sym] = File.open(@options[key]) { |f| YAML.load(f.read) }
|
107
|
+
end
|
108
|
+
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
def configuration_directory_exists?
|
113
|
+
unless File.exists?(Colloquy.root) and
|
114
|
+
File.directory?(Colloquy.root) and
|
115
|
+
File.directory?(@options[:path_config])
|
116
|
+
raise Colloquy::ConfigurationFolderNotFound,
|
117
|
+
"Cannot find #{Colloquy.root} to load configuration from.\
|
118
|
+
Renderer needs a valid root path to read config/flows.yaml and config/logger.yaml"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Does lots of initial setup.
|
123
|
+
# @private
|
124
|
+
def configure
|
125
|
+
initialize_logger
|
126
|
+
|
127
|
+
load_paths
|
128
|
+
load_flows
|
129
|
+
load_messages
|
130
|
+
|
131
|
+
set_maximum_message_length
|
132
|
+
set_maximum_unicode_length
|
133
|
+
set_flow_pool_size
|
134
|
+
|
135
|
+
initialize_session
|
136
|
+
initialize_state
|
137
|
+
|
138
|
+
load_flow_pool
|
139
|
+
end
|
140
|
+
|
141
|
+
def flows_yaml_exists?
|
142
|
+
@options[:path_flows_yaml] = @options[:path_config].join('flows.yaml')
|
143
|
+
|
144
|
+
unless @options[:path_flows_yaml].exist?
|
145
|
+
raise Colloquy::FlowConfigurationNotFound, "Cannot find flows yaml in #{@options[:path_flow_yaml]}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def logger_yaml_exists?
|
150
|
+
@options[:path_logger_yaml] = @options[:path_config].join('logger.yaml')
|
151
|
+
|
152
|
+
unless @options[:path_logger_yaml].exist?
|
153
|
+
raise Colloquy::LoggerConfigurationNotFound, "Cannot find flows yaml in #{@options[:path_flow_yaml]}"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def messages_yaml_exists?
|
158
|
+
@options[:path_messages_yaml] = @options[:path_config].join('messages.yaml')
|
159
|
+
|
160
|
+
unless @options[:path_messages_yaml].exist?
|
161
|
+
raise Colloquy::MessagesConfigurationNotFound, "Cannot find messages yaml in #{@options[:path_messages_yaml]}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Infer messages yaml path from flow name
|
166
|
+
# @param [String] flow_name Flow name
|
167
|
+
# @return [Pathname] Path to messages yaml corresponding to flow name
|
168
|
+
def messages_file_name(flow_name)
|
169
|
+
Colloquy.root.join('messages', "#{flow_name}.yaml")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Configure and create logger instance
|
173
|
+
def initialize_logger
|
174
|
+
logger_file = Pathname.new(Colloquy.root.join(@options[:logger][:path]))
|
175
|
+
log_level = if @options[:verbose]
|
176
|
+
:DEBUG
|
177
|
+
else
|
178
|
+
@options[:logger][:log_level]
|
179
|
+
end.upcase.to_sym
|
180
|
+
|
181
|
+
raise Colloquy::LogDirectoryNotPresent unless logger_file
|
182
|
+
|
183
|
+
@logger = Colloquy::Logger.new(logger_file)
|
184
|
+
@logger.info 'Renderer starting up...'
|
185
|
+
@logger.info "Log level is #{log_level}"
|
186
|
+
@logger.level = ActiveSupport::Logger::Severity.const_get(log_level)
|
187
|
+
|
188
|
+
# Set this up as a logger available at the root
|
189
|
+
Colloquy.logger = @logger
|
190
|
+
end
|
191
|
+
|
192
|
+
# Load load_paths configured in flows yaml into ruby load path
|
193
|
+
def load_paths
|
194
|
+
@options[:flows][:load_paths].each do |path|
|
195
|
+
flow_path = Pathname.new(path)
|
196
|
+
$: << if flow_path.relative?
|
197
|
+
Colloquy.root.join(flow_path)
|
198
|
+
else
|
199
|
+
flow_path
|
200
|
+
end.realpath.to_s
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Load all active flow specific messages into flow
|
205
|
+
def load_messages
|
206
|
+
@options[:flows][:active].each do |flow_entry|
|
207
|
+
load_messages_into_flow(flow_entry)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Load flow specific messages into flow
|
212
|
+
# @param [String] flow_entry Entry in flows yaml
|
213
|
+
def load_messages_into_flow(flow_entry)
|
214
|
+
flow_messages = @options[:messages]
|
215
|
+
_, flow_name, _ = extract_flow_components_from_entry(flow_entry)
|
216
|
+
|
217
|
+
if messages_file_name(flow_name).exist?
|
218
|
+
messages = File.open(messages_file_name(flow_name)) { |f| YAML.load(f.read) }
|
219
|
+
messages ||= {}
|
220
|
+
messages = messages.with_indifferent_access
|
221
|
+
|
222
|
+
flow_messages = flow_messages.merge(messages)
|
223
|
+
end
|
224
|
+
|
225
|
+
@flows[flow_name.to_sym].messages = flow_messages
|
226
|
+
end
|
227
|
+
|
228
|
+
# Create a hash with all active flows mapped to their instances
|
229
|
+
# @return [Hash] A Hash with all flows and instances
|
230
|
+
def load_flows
|
231
|
+
@flows = {}
|
232
|
+
|
233
|
+
@options[:flows][:active].each do |flow_entry|
|
234
|
+
load_flow(flow_entry)
|
235
|
+
end
|
236
|
+
|
237
|
+
@flows
|
238
|
+
end
|
239
|
+
|
240
|
+
def load_flow_pool
|
241
|
+
@options[:flows][:active].each do |flow_entry|
|
242
|
+
_, flow_name, flow_class = extract_flow_components_from_entry(flow_entry)
|
243
|
+
messages = @flows[flow_name.to_sym].messages
|
244
|
+
Colloquy::Renderer::FlowPool.create_flows(flow_name, flow_class, @flow_pool_size, logger: @logger, messages: messages)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Sets maximum messages length, restricted to 160 for by telecom Provider
|
249
|
+
def set_maximum_message_length
|
250
|
+
Colloquy.maximum_message_length = (@options[:flows][:maximum_message_length] || 160).to_i
|
251
|
+
end
|
252
|
+
|
253
|
+
# Sets maximum unicode length, restricted to 70 as unicode uses two bytes per character
|
254
|
+
def set_maximum_unicode_length
|
255
|
+
Colloquy.maximum_unicode_length = (@options[:flows][:maximum_unicode_length] || 70).to_i
|
256
|
+
end
|
257
|
+
|
258
|
+
def set_flow_pool_size
|
259
|
+
@flow_pool_size = (@options[:flows][:flow_pool_size] || 50).to_i
|
260
|
+
end
|
261
|
+
|
262
|
+
# This is where the input is actually passed on to FlowParser#apply for processing.
|
263
|
+
#
|
264
|
+
# This method taps into flow object and injects the current state, session and headers
|
265
|
+
# into it. This modifed object is then passed to FlowParser#apply
|
266
|
+
# @return [Response] The response returned from FlowParser#apply
|
267
|
+
def apply!(flow, state, session, session_key, flow_name, msisdn, session_id, input = nil)
|
268
|
+
# set flow state and session correctly, reset all nodes
|
269
|
+
flow = prime_flow(flow, state, session, flow_name, msisdn, session_id, input)
|
270
|
+
|
271
|
+
# apply and get the response
|
272
|
+
response = flow.apply(input)
|
273
|
+
|
274
|
+
# store the state and session from the applied flow
|
275
|
+
state_reset_from_flow(flow, flow_name, session_key)
|
276
|
+
session_reset_from_flow(flow, flow_name, session_key)
|
277
|
+
|
278
|
+
# return the response
|
279
|
+
response
|
280
|
+
rescue Colloquy::SwitchFlowJump => e
|
281
|
+
# add flow back into flow pool before switching to new flow
|
282
|
+
Colloquy::Renderer::FlowPool.add_flow(flow.flow_name, flow)
|
283
|
+
session_reset_from_flow(flow, flow_name, session_key)
|
284
|
+
session_switch_flow(session_key, flow_name, e.payload[:flow], e.payload[:node])
|
285
|
+
state, session, flow = state_load(flow_name, session_key)
|
286
|
+
|
287
|
+
retry
|
288
|
+
rescue Colloquy::FlowPoolEmpty
|
289
|
+
flow = nil #explicitly marked as nil so that its not added back into the pool
|
290
|
+
raise
|
291
|
+
ensure
|
292
|
+
# add flow back into flow pool
|
293
|
+
Colloquy::Renderer::FlowPool.add_flow(flow.flow_name, flow) if flow
|
294
|
+
end
|
295
|
+
|
296
|
+
def prime_flow(flow, state, session, flow_name, msisdn, session_id, input)
|
297
|
+
flow.tap do |f|
|
298
|
+
f.state = state
|
299
|
+
f.session = session
|
300
|
+
f.headers = {
|
301
|
+
flow_name: flow_name,
|
302
|
+
msisdn: msisdn,
|
303
|
+
session_id: session_id,
|
304
|
+
input: input,
|
305
|
+
page: (state[:page] || 1),
|
306
|
+
metadata: (state[:metadata] || {})
|
307
|
+
}
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def state_reset_from_flow(flow, flow_name, session_key)
|
312
|
+
@state[flow_name.to_sym][session_key] = flow.send(:state) # state is private
|
313
|
+
end
|
314
|
+
|
315
|
+
def session_reset_from_flow(flow, flow_name, session_key)
|
316
|
+
@session[flow_name.to_sym][session_key] = flow.session
|
317
|
+
end
|
318
|
+
|
319
|
+
def is_flow?(flow)
|
320
|
+
flow.is_a? Colloquy::FlowParser
|
321
|
+
end
|
322
|
+
|
323
|
+
def session_switch_flow(session_key, from, to, node_to = :index)
|
324
|
+
from_flow = is_flow?(from) ? from : @flows[from]
|
325
|
+
to_flow = is_flow?(to) ? to : @flows[to]
|
326
|
+
node_to ||= :index
|
327
|
+
|
328
|
+
unless is_flow?(to_flow)
|
329
|
+
raise Colloquy::JumpInvalidException, "Cannot find flow #{to} to switch to"
|
330
|
+
end
|
331
|
+
|
332
|
+
current_state = @state[from_flow.flow_name][session_key]
|
333
|
+
|
334
|
+
new_state = {
|
335
|
+
:switched_from => current_state,
|
336
|
+
:flow_name => to_flow.flow_name,
|
337
|
+
:node => node_to,
|
338
|
+
:flow_state => :init
|
339
|
+
}
|
340
|
+
|
341
|
+
# switch!
|
342
|
+
@state[from_flow.flow_name][session_key] = new_state
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
def state_load(flow_name, session_key, metadata = {})
|
347
|
+
flow_name = flow_name.to_sym
|
348
|
+
|
349
|
+
# extract the state if it's present
|
350
|
+
flow_state = (@state[flow_name][session_key] || {})
|
351
|
+
flow_session = (@session[flow_name][session_key] || {})
|
352
|
+
|
353
|
+
# Now, load the flow_pool_key from the flow_state if set
|
354
|
+
flow_pool_key = (flow_state[:flow_name] || flow_name).to_sym
|
355
|
+
|
356
|
+
# Pop a FlowParser object from flow pool
|
357
|
+
flow = Colloquy::Renderer::FlowPool.pop_flow(flow_pool_key)
|
358
|
+
raise Colloquy::FlowPoolEmpty, 'Flow pool is empty' unless flow
|
359
|
+
|
360
|
+
# Merge in the metadata
|
361
|
+
flow_state[:metadata] = metadata
|
362
|
+
|
363
|
+
[flow_state, flow_session, flow]
|
364
|
+
end
|
365
|
+
|
366
|
+
# Load, instantiate and save that instance in an instance variable
|
367
|
+
# @param [String, Hash] flow_entry Flow entry specified in flows yaml
|
368
|
+
def load_flow(flow_entry)
|
369
|
+
flow_path, flow_name, flow_class = extract_flow_components_from_entry(flow_entry)
|
370
|
+
|
371
|
+
# we load the flow dynamically by the earlier set load_path
|
372
|
+
require "#{flow_path}_flow"
|
373
|
+
|
374
|
+
@flows[flow_name.to_sym] = flow_class.constantize.new(flow_name, logger: @logger)
|
375
|
+
end
|
376
|
+
|
377
|
+
# Extract path, name and class of flow using entry in flows yaml
|
378
|
+
# @param [String, Hash] flow_entry Flow entry specified in flows yaml
|
379
|
+
# @return [Array] Path, Name and Class of given flow
|
380
|
+
def extract_flow_components_from_entry(flow_entry)
|
381
|
+
if flow_entry.is_a? Hash
|
382
|
+
flow_path = flow_entry.to_a.flatten.first
|
383
|
+
flow_name = flow_path.split('/').last
|
384
|
+
flow_class = flow_entry.to_a.flatten.last
|
385
|
+
else
|
386
|
+
flow_path = flow_entry
|
387
|
+
flow_name = flow_entry.split('/').last
|
388
|
+
flow_class = "#{flow_name}_flow".classify
|
389
|
+
end
|
390
|
+
|
391
|
+
[flow_path, flow_name, flow_class]
|
392
|
+
end
|
393
|
+
|
394
|
+
def initialize_session
|
395
|
+
@session ||= {}
|
396
|
+
|
397
|
+
@flows.each do |flow_name, flow|
|
398
|
+
@session[flow_name.to_sym] ||= session_store
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
def initialize_state
|
403
|
+
@state ||= {}
|
404
|
+
|
405
|
+
@flows.each do |flow_name, flow|
|
406
|
+
@state[flow_name.to_sym] ||= state_store
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def session_store
|
411
|
+
Colloquy::SessionStore.haystack(@options[:flows][:session_store] || :memory, :identifier => :sessions)
|
412
|
+
end
|
413
|
+
|
414
|
+
def state_store
|
415
|
+
Colloquy::SessionStore.haystack(@options[:flows][:session_store] || :memory, :identifier => :state)
|
416
|
+
end
|
417
|
+
|
418
|
+
# @param [String, String] msisdn, session_id
|
419
|
+
# @return [String] session_key
|
420
|
+
def make_session_key(msisdn, session_id)
|
421
|
+
"#{msisdn}-#{session_id}"
|
422
|
+
end
|
423
|
+
end
|