stealth 1.1.2 → 2.0.0.beta1
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/.circleci/config.yml +18 -8
- data/CHANGELOG.md +100 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +49 -43
- data/LICENSE +4 -17
- data/README.md +9 -17
- data/VERSION +1 -1
- data/lib/stealth/base.rb +62 -13
- data/lib/stealth/cli.rb +1 -2
- data/lib/stealth/commands/console.rb +1 -1
- data/lib/stealth/configuration.rb +0 -3
- data/lib/stealth/controller/callbacks.rb +1 -1
- data/lib/stealth/controller/catch_all.rb +27 -4
- data/lib/stealth/controller/controller.rb +168 -49
- data/lib/stealth/controller/dev_jumps.rb +41 -0
- data/lib/stealth/controller/dynamic_delay.rb +4 -6
- data/lib/stealth/controller/interrupt_detect.rb +100 -0
- data/lib/stealth/controller/messages.rb +283 -0
- data/lib/stealth/controller/nlp.rb +50 -0
- data/lib/stealth/controller/replies.rb +179 -41
- data/lib/stealth/controller/unrecognized_message.rb +62 -0
- data/lib/stealth/core_ext.rb +5 -0
- data/lib/stealth/{flow/core_ext.rb → core_ext/numeric.rb} +0 -1
- data/lib/stealth/core_ext/string.rb +18 -0
- data/lib/stealth/dispatcher.rb +21 -0
- data/lib/stealth/errors.rb +12 -0
- data/lib/stealth/flow/base.rb +1 -2
- data/lib/stealth/flow/specification.rb +3 -2
- data/lib/stealth/flow/state.rb +3 -3
- data/lib/stealth/generators/builder/Gemfile +4 -3
- data/lib/stealth/generators/builder/bot/controllers/bot_controller.rb +42 -0
- data/lib/stealth/generators/builder/bot/controllers/catch_alls_controller.rb +2 -0
- data/lib/stealth/generators/builder/bot/controllers/goodbyes_controller.rb +2 -0
- data/lib/stealth/generators/builder/bot/controllers/hellos_controller.rb +2 -0
- data/lib/stealth/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
- data/lib/stealth/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
- data/lib/stealth/generators/builder/config/flow_map.rb +8 -0
- data/lib/stealth/generators/builder/config/initializers/autoload.rb +8 -0
- data/lib/stealth/generators/builder/config/initializers/inflections.rb +16 -0
- data/lib/stealth/generators/builder/config/puma.rb +15 -0
- data/lib/stealth/helpers/redis.rb +40 -0
- data/lib/stealth/lock.rb +83 -0
- data/lib/stealth/logger.rb +27 -18
- data/lib/stealth/nlp/client.rb +22 -0
- data/lib/stealth/nlp/result.rb +57 -0
- data/lib/stealth/reloader.rb +90 -0
- data/lib/stealth/reply.rb +17 -0
- data/lib/stealth/scheduled_reply.rb +3 -3
- data/lib/stealth/server.rb +4 -4
- data/lib/stealth/service_message.rb +3 -2
- data/lib/stealth/service_reply.rb +5 -1
- data/lib/stealth/services/base_reply_handler.rb +2 -2
- data/lib/stealth/session.rb +106 -53
- data/spec/configuration_spec.rb +9 -2
- data/spec/controller/callbacks_spec.rb +23 -28
- data/spec/controller/catch_all_spec.rb +81 -29
- data/spec/controller/controller_spec.rb +444 -43
- data/spec/controller/dynamic_delay_spec.rb +16 -18
- data/spec/controller/helpers_spec.rb +1 -2
- data/spec/controller/interrupt_detect_spec.rb +171 -0
- data/spec/controller/messages_spec.rb +744 -0
- data/spec/controller/nlp_spec.rb +93 -0
- data/spec/controller/replies_spec.rb +446 -11
- data/spec/controller/unrecognized_message_spec.rb +168 -0
- data/spec/dispatcher_spec.rb +79 -0
- data/spec/flow/flow_spec.rb +1 -2
- data/spec/flow/state_spec.rb +14 -3
- data/spec/helpers/redis_spec.rb +77 -0
- data/spec/lock_spec.rb +100 -0
- data/spec/nlp/client_spec.rb +23 -0
- data/spec/nlp/result_spec.rb +57 -0
- data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
- data/spec/replies/messages/say_randomize_speech.yml +10 -0
- data/spec/replies/messages/say_randomize_text.yml +10 -0
- data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
- data/spec/reply_spec.rb +61 -0
- data/spec/scheduled_reply_spec.rb +23 -0
- data/spec/service_reply_spec.rb +1 -2
- data/spec/session_spec.rb +251 -12
- data/spec/spec_helper.rb +21 -0
- data/spec/support/controllers/vaders_controller.rb +24 -0
- data/spec/support/nlp_clients/dialogflow.rb +9 -0
- data/spec/support/nlp_clients/luis.rb +9 -0
- data/spec/support/nlp_results/luis_result.rb +163 -0
- data/spec/version_spec.rb +1 -2
- data/stealth.gemspec +6 -6
- metadata +83 -39
- data/docs/00-introduction.md +0 -37
- data/docs/01-getting-started.md +0 -21
- data/docs/02-local-development.md +0 -40
- data/docs/03-basics.md +0 -171
- data/docs/04-sessions.md +0 -29
- data/docs/05-controllers.md +0 -179
- data/docs/06-models.md +0 -39
- data/docs/07-replies.md +0 -114
- data/docs/08-catchalls.md +0 -49
- data/docs/09-messaging-integrations.md +0 -80
- data/docs/10-nlp-integrations.md +0 -13
- data/docs/11-analytics.md +0 -13
- data/docs/12-commands.md +0 -62
- data/docs/13-deployment.md +0 -50
- data/lib/stealth/generators/builder/config/initializers/.keep +0 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
class Controller
|
6
|
+
module UnrecognizedMessage
|
7
|
+
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
|
12
|
+
def run_unrecognized_message(err:)
|
13
|
+
err_message = "The message \"#{current_message.message}\" was not recognized in the original context."
|
14
|
+
|
15
|
+
Stealth::Logger.l(
|
16
|
+
topic: 'unrecognized_message',
|
17
|
+
message: err_message
|
18
|
+
)
|
19
|
+
|
20
|
+
unless defined?(UnrecognizedMessagesController)
|
21
|
+
Stealth::Logger.l(
|
22
|
+
topic: 'unrecognized_message',
|
23
|
+
message: 'Running catch_all; UnrecognizedMessagesController not defined.'
|
24
|
+
)
|
25
|
+
|
26
|
+
run_catch_all(err: err)
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
unrecognized_msg_controller = UnrecognizedMessagesController.new(
|
31
|
+
service_message: current_message
|
32
|
+
)
|
33
|
+
|
34
|
+
begin
|
35
|
+
# Run handle_unrecognized_message action
|
36
|
+
unrecognized_msg_controller.handle_unrecognized_message
|
37
|
+
|
38
|
+
if unrecognized_msg_controller.progressed?
|
39
|
+
Stealth::Logger.l(
|
40
|
+
topic: 'unrecognized_message',
|
41
|
+
message: 'A match was detected. Skipping catch-all.'
|
42
|
+
)
|
43
|
+
else
|
44
|
+
# Log, but we don't want to run the catch_all for a poorly
|
45
|
+
# coded UnrecognizedMessagesController
|
46
|
+
Stealth::Logger.l(
|
47
|
+
topic: 'unrecognized_message',
|
48
|
+
message: 'Did not send replies, update session, or step'
|
49
|
+
)
|
50
|
+
end
|
51
|
+
rescue StandardError => e
|
52
|
+
# Run the catch_all directly since we're already in an unrecognized
|
53
|
+
# message state
|
54
|
+
run_catch_all(err: e)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class String
|
4
|
+
|
5
|
+
EXCLUDED_CHARS = %w[" ' . , ! ? ( ) - _ ` ‘ ’ “ ”].freeze
|
6
|
+
EXCLUDED_CHARS_ESC = EXCLUDED_CHARS.map { |c| "\\#{c}" }
|
7
|
+
EXCLUDED_CHARS_RE = /#{EXCLUDED_CHARS_ESC.join('|')}/
|
8
|
+
|
9
|
+
# Removes blank padding and double+single quotes
|
10
|
+
def normalize
|
11
|
+
self.upcase.strip
|
12
|
+
end
|
13
|
+
|
14
|
+
def without_punctuation
|
15
|
+
self.gsub(EXCLUDED_CHARS_RE, '')
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/lib/stealth/dispatcher.rb
CHANGED
@@ -30,6 +30,11 @@ module Stealth
|
|
30
30
|
|
31
31
|
def process
|
32
32
|
service_message = message_handler.process
|
33
|
+
|
34
|
+
if Stealth.config.transcript_logging
|
35
|
+
log_incoming_message(service_message)
|
36
|
+
end
|
37
|
+
|
33
38
|
bot_controller = BotController.new(service_message: service_message)
|
34
39
|
bot_controller.route
|
35
40
|
end
|
@@ -44,5 +49,21 @@ module Stealth
|
|
44
49
|
end
|
45
50
|
end
|
46
51
|
|
52
|
+
def log_incoming_message(service_message)
|
53
|
+
message = if service_message.location.present?
|
54
|
+
"Received: <user shared location>"
|
55
|
+
elsif service_message.attachments.present?
|
56
|
+
"Received: <user sent attachment>"
|
57
|
+
elsif service_message.payload.present?
|
58
|
+
"Received Payload: #{service_message.payload}"
|
59
|
+
else
|
60
|
+
"Received Message: #{service_message.message}"
|
61
|
+
end
|
62
|
+
|
63
|
+
Stealth::Logger.l(
|
64
|
+
topic: 'user',
|
65
|
+
message: "User #{service_message.sender_id} -> #{message}"
|
66
|
+
)
|
67
|
+
end
|
47
68
|
end
|
48
69
|
end
|
data/lib/stealth/errors.rb
CHANGED
@@ -34,11 +34,23 @@ module Stealth
|
|
34
34
|
class ReplyNotFound < Errors
|
35
35
|
end
|
36
36
|
|
37
|
+
class UnrecognizedMessage < Errors
|
38
|
+
end
|
39
|
+
|
37
40
|
class FlowError < Errors
|
38
41
|
end
|
39
42
|
|
40
43
|
class FlowDefinitionError < Errors
|
41
44
|
end
|
42
45
|
|
46
|
+
class InvalidSessionID < Errors
|
47
|
+
end
|
48
|
+
|
49
|
+
class UserOptOut < Errors
|
50
|
+
end
|
51
|
+
|
52
|
+
class ReservedHomophoneUsed < Errors
|
53
|
+
end
|
54
|
+
|
43
55
|
end
|
44
56
|
end
|
data/lib/stealth/flow/base.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'stealth/flow/core_ext'
|
5
4
|
require 'stealth/flow/specification'
|
6
5
|
require 'stealth/flow/state'
|
7
6
|
|
@@ -55,7 +54,7 @@ module Stealth
|
|
55
54
|
private
|
56
55
|
|
57
56
|
def flow_and_state
|
58
|
-
[current_flow, current_state].join(
|
57
|
+
[current_flow, current_state].join(Stealth::Session::SLUG_SEPARATOR)
|
59
58
|
end
|
60
59
|
|
61
60
|
def state_exists?(potential_flow:, potential_state:)
|
@@ -19,7 +19,7 @@ module Stealth
|
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
def state(name, fails_to: nil, redirects_to: nil)
|
22
|
+
def state(name, fails_to: nil, redirects_to: nil, **opts)
|
23
23
|
fail_state = get_fail_or_redirect_state(fails_to)
|
24
24
|
redirect_state = get_fail_or_redirect_state(redirects_to)
|
25
25
|
|
@@ -27,7 +27,8 @@ module Stealth
|
|
27
27
|
name: name,
|
28
28
|
spec: self,
|
29
29
|
fails_to: fail_state,
|
30
|
-
redirects_to: redirect_state
|
30
|
+
redirects_to: redirect_state,
|
31
|
+
opts: opts
|
31
32
|
)
|
32
33
|
|
33
34
|
@initial_state = new_state if @states.empty?
|
data/lib/stealth/flow/state.rb
CHANGED
@@ -8,9 +8,9 @@ module Stealth
|
|
8
8
|
include Comparable
|
9
9
|
|
10
10
|
attr_accessor :name
|
11
|
-
attr_reader :spec, :fails_to, :redirects_to
|
11
|
+
attr_reader :spec, :fails_to, :redirects_to, :opts
|
12
12
|
|
13
|
-
def initialize(name:, spec:, fails_to: nil, redirects_to: nil)
|
13
|
+
def initialize(name:, spec:, fails_to: nil, redirects_to: nil, opts:)
|
14
14
|
if fails_to.present? && !fails_to.is_a?(Stealth::Session)
|
15
15
|
raise(ArgumentError, 'fails_to state should be a Stealth::Session')
|
16
16
|
end
|
@@ -20,7 +20,7 @@ module Stealth
|
|
20
20
|
end
|
21
21
|
|
22
22
|
@name, @spec = name, spec
|
23
|
-
@fails_to, @redirects_to = fails_to, redirects_to
|
23
|
+
@fails_to, @redirects_to, @opts = fails_to, redirects_to, opts
|
24
24
|
end
|
25
25
|
|
26
26
|
def <=>(other_state)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gem 'stealth', '~>
|
3
|
+
gem 'stealth', '~> 2.0'
|
4
4
|
|
5
|
-
gem 'railties', '~>
|
6
|
-
gem 'activerecord', '~>
|
5
|
+
gem 'railties', '~> 6.0'
|
6
|
+
gem 'activerecord', '~> 6.0'
|
7
7
|
# Use sqlite3 as the database for Active Record
|
8
8
|
gem 'sqlite3'
|
9
9
|
|
@@ -15,4 +15,5 @@ gem 'sqlite3'
|
|
15
15
|
|
16
16
|
group :development do
|
17
17
|
gem 'foreman'
|
18
|
+
gem 'listen', '~> 3.1'
|
18
19
|
end
|
@@ -1,8 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class BotController < Stealth::Controller
|
2
4
|
|
3
5
|
helper :all
|
4
6
|
|
5
7
|
def route
|
8
|
+
if current_message.payload.present?
|
9
|
+
handle_payloads
|
10
|
+
# Clear out the payload to prevent duplicate handling
|
11
|
+
current_message.payload = nil
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
# Allow devs to jump around flows and states by typing:
|
16
|
+
# /flow_name/state_name or
|
17
|
+
# /flow_name (jumps to first state) or
|
18
|
+
# //state_name (jumps to state in current flow)
|
19
|
+
# (only works for bots in development)
|
20
|
+
return if dev_jump_detected?
|
21
|
+
|
6
22
|
if current_session.present?
|
7
23
|
step_to session: current_session
|
8
24
|
else
|
@@ -10,4 +26,30 @@ class BotController < Stealth::Controller
|
|
10
26
|
end
|
11
27
|
end
|
12
28
|
|
29
|
+
private
|
30
|
+
|
31
|
+
# Handle payloads globally since payload buttons remain in the chat
|
32
|
+
# and we cannot guess in which states they will be tapped.
|
33
|
+
def handle_payloads
|
34
|
+
case current_message.payload
|
35
|
+
when 'developer_restart', 'new_user'
|
36
|
+
step_to flow: 'hello', state: 'say_hello'
|
37
|
+
when 'goodbye'
|
38
|
+
step_to flow: 'goodbye'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Automatically called when clients receive an opt-out error from
|
43
|
+
# the platform. You can write your own steps for handling.
|
44
|
+
def handle_opt_out
|
45
|
+
do_nothing
|
46
|
+
end
|
47
|
+
|
48
|
+
# Automatically called when clients receive an invalid session_id error from
|
49
|
+
# the platform. For example, attempting to text a landline.
|
50
|
+
# You can write your own steps for handling.
|
51
|
+
def handle_invalid_session_id
|
52
|
+
do_nothing
|
53
|
+
end
|
54
|
+
|
13
55
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Add additional directories below for hot-reloading during development.
|
2
|
+
# You'll want to include any custom directories you create for your code.
|
3
|
+
# To stop certain files or directories from autoloading, use autoload_ignore_paths
|
4
|
+
|
5
|
+
# Stealth.config.autoload_paths << File.join(Stealth.root, 'bot', 'services')
|
6
|
+
# Stealth.config.autoload_paths << File.join(Stealth.root, 'bot', 'jobs')
|
7
|
+
|
8
|
+
# Stealth.config.autoload_ignore_paths << File.join(Stealth.root, 'bot', 'overrides')
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Add new inflection rules using the following format. Inflections
|
4
|
+
# are locale specific, and you may define rules for as many different
|
5
|
+
# locales as you wish. All of these examples are active by default:
|
6
|
+
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
7
|
+
# inflect.plural /^(ox)$/i, '\1en'
|
8
|
+
# inflect.singular /^(ox)en/i, '\1'
|
9
|
+
# inflect.irregular 'person', 'people'
|
10
|
+
# inflect.uncountable %w( fish sheep )
|
11
|
+
# end
|
12
|
+
|
13
|
+
# These inflection rules are supported but not enabled by default:
|
14
|
+
# ActiveSupport::Inflector.inflections(:en) do |inflect|
|
15
|
+
# inflect.acronym 'RESTful'
|
16
|
+
# end
|
@@ -5,6 +5,21 @@ threads threads_count, threads_count
|
|
5
5
|
#
|
6
6
|
port ENV.fetch("PORT") { 3000 }
|
7
7
|
|
8
|
+
# Specifies the number of `workers` to boot in clustered mode.
|
9
|
+
# Workers are forked webserver processes. If using threads and workers together
|
10
|
+
# the concurrency of the application would be max `threads` * `workers`.
|
11
|
+
# Workers do not work on JRuby or Windows (both of which do not support
|
12
|
+
# processes).
|
13
|
+
#
|
14
|
+
# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
15
|
+
|
16
|
+
# Use the `preload_app!` method when specifying a `workers` number.
|
17
|
+
# This directive tells Puma to first boot the application and load code
|
18
|
+
# before forking the application. This takes advantage of Copy On Write
|
19
|
+
# process behavior so workers use less memory.
|
20
|
+
#
|
21
|
+
# preload_app!
|
22
|
+
|
8
23
|
# Specifies the `environment` that Puma will run in.
|
9
24
|
#
|
10
25
|
environment ENV.fetch("STEALTH_ENV") { "development" }
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
module Redis
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
private
|
10
|
+
|
11
|
+
def get_key(key, expiration: Stealth.config.session_ttl)
|
12
|
+
if expiration > 0
|
13
|
+
getex(key, expiration)
|
14
|
+
else
|
15
|
+
$redis.get(key)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete_key(key)
|
20
|
+
$redis.del(key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def getex(key, expiration=Stealth.config.session_ttl)
|
24
|
+
$redis.multi do
|
25
|
+
$redis.expire(key, expiration)
|
26
|
+
$redis.get(key)
|
27
|
+
end.last
|
28
|
+
end
|
29
|
+
|
30
|
+
def persist_key(key:, value:, expiration: Stealth.config.session_ttl)
|
31
|
+
if expiration > 0
|
32
|
+
$redis.setex(key, expiration, value)
|
33
|
+
else
|
34
|
+
$redis.set(key, value)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/stealth/lock.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Stealth
|
5
|
+
class Lock
|
6
|
+
|
7
|
+
include Stealth::Redis
|
8
|
+
|
9
|
+
attr_accessor :session_id, :session_slug, :position, :tid
|
10
|
+
|
11
|
+
def initialize(session_id:, session_slug: nil, position: nil)
|
12
|
+
@session_id = session_id
|
13
|
+
@session_slug = session_slug
|
14
|
+
@position = position
|
15
|
+
@tid = Stealth.tid
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_lock(session_id:)
|
19
|
+
lock = Lock.new(session_id: session_id)
|
20
|
+
lock_slug = lock.slug # fetch lock from Redis
|
21
|
+
|
22
|
+
return if lock_slug.nil?
|
23
|
+
|
24
|
+
# parse the lock slug
|
25
|
+
tid_and_session_slug, position = lock_slug.split(':')
|
26
|
+
tid, session_slug = tid_and_session_slug.split('#')
|
27
|
+
|
28
|
+
# set the values from the slug to the lock object
|
29
|
+
lock.session_slug = session_slug
|
30
|
+
lock.position = position&.to_i
|
31
|
+
lock.tid = tid
|
32
|
+
lock
|
33
|
+
end
|
34
|
+
|
35
|
+
def create
|
36
|
+
if session_slug.blank?
|
37
|
+
raise(
|
38
|
+
ArgumentError,
|
39
|
+
'A session_slug must be specified before a lock can be created.'
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Expire locks after 30 seconds to prevent zombie locks from blocking
|
44
|
+
# other threads to interact with a session.
|
45
|
+
persist_key(
|
46
|
+
key: lock_key,
|
47
|
+
value: generate_lock,
|
48
|
+
expiration: Stealth.config.lock_autorelease
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def release
|
53
|
+
delete_key(lock_key)
|
54
|
+
end
|
55
|
+
|
56
|
+
def slug
|
57
|
+
# We don't want to extend the expiration time that would result if
|
58
|
+
# we specified one here.
|
59
|
+
get_key(lock_key, expiration: 0)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns a hash:
|
63
|
+
# { flow: 'flow_name', state: 'state_name' }
|
64
|
+
def flow_and_state
|
65
|
+
Session.flow_and_state_from_session_slug(slug: session_slug)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def lock_key
|
71
|
+
[@session_id, 'lock'].join('-')
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_lock
|
75
|
+
if @position.present?
|
76
|
+
@session_slug = [@session_slug, @position].join(':')
|
77
|
+
end
|
78
|
+
|
79
|
+
[@tid, @session_slug].join('#')
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|