mbuzz 0.6.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +92 -0
- data/LICENSE.txt +21 -0
- data/README.md +173 -0
- data/Rakefile +8 -0
- data/lib/mbuzz/api.rb +102 -0
- data/lib/mbuzz/client/conversion_request.rb +85 -0
- data/lib/mbuzz/client/identify_request.rb +37 -0
- data/lib/mbuzz/client/session_request.rb +43 -0
- data/lib/mbuzz/client/track_request.rb +50 -0
- data/lib/mbuzz/client.rb +36 -0
- data/lib/mbuzz/configuration.rb +51 -0
- data/lib/mbuzz/controller_helpers.rb +34 -0
- data/lib/mbuzz/middleware/tracking.rb +157 -0
- data/lib/mbuzz/railtie.rb +13 -0
- data/lib/mbuzz/request_context.rb +40 -0
- data/lib/mbuzz/version.rb +5 -0
- data/lib/mbuzz/visitor/identifier.rb +13 -0
- data/lib/mbuzz.rb +178 -0
- data/lib/specs/old/SPECIFICATION.md +695 -0
- data/lib/specs/old/conversions.md +585 -0
- data/lib/specs/old/event_ids_response.md +346 -0
- data/lib/specs/old/v0.2.0_breaking_changes.md +519 -0
- data/lib/specs/old/v2.0.0_sessions_upgrade.md +265 -0
- data/lib/specs/v0.5.0_four_call_model.md +505 -0
- data/mbuzz-0.6.0.gem +0 -0
- data/sig/mbuzz.rbs +4 -0
- metadata +89 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class ConfigurationError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :api_key, :api_url, :enabled, :debug, :timeout, :batch_size, :flush_interval, :logger,
|
|
8
|
+
:skip_paths, :skip_extensions
|
|
9
|
+
|
|
10
|
+
# Default paths to skip - health checks, assets, etc.
|
|
11
|
+
DEFAULT_SKIP_PATHS = %w[
|
|
12
|
+
/up
|
|
13
|
+
/health
|
|
14
|
+
/healthz
|
|
15
|
+
/ping
|
|
16
|
+
/cable
|
|
17
|
+
/assets
|
|
18
|
+
/packs
|
|
19
|
+
/rails/active_storage
|
|
20
|
+
/api
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
# Default extensions to skip - static assets
|
|
24
|
+
DEFAULT_SKIP_EXTENSIONS = %w[
|
|
25
|
+
.js .css .map .png .jpg .jpeg .gif .ico .svg .woff .woff2 .ttf .eot .webp
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
def initialize
|
|
29
|
+
@api_url = "https://mbuzz.co/api/v1"
|
|
30
|
+
@enabled = true
|
|
31
|
+
@debug = false
|
|
32
|
+
@timeout = 5
|
|
33
|
+
@batch_size = 50
|
|
34
|
+
@flush_interval = 30
|
|
35
|
+
@skip_paths = []
|
|
36
|
+
@skip_extensions = []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def validate!
|
|
40
|
+
raise ConfigurationError, "api_key is required" if api_key.nil? || api_key.empty?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def all_skip_paths
|
|
44
|
+
DEFAULT_SKIP_PATHS + Array(skip_paths)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def all_skip_extensions
|
|
48
|
+
DEFAULT_SKIP_EXTENSIONS + Array(skip_extensions)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
module ControllerHelpers
|
|
5
|
+
def mbuzz_track(event_type, properties: {})
|
|
6
|
+
Client.track(
|
|
7
|
+
user_id: mbuzz_user_id,
|
|
8
|
+
visitor_id: mbuzz_visitor_id,
|
|
9
|
+
event_type: event_type,
|
|
10
|
+
properties: properties
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def mbuzz_identify(traits: {})
|
|
15
|
+
Client.identify(
|
|
16
|
+
user_id: mbuzz_user_id,
|
|
17
|
+
visitor_id: mbuzz_visitor_id,
|
|
18
|
+
traits: traits
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def mbuzz_user_id
|
|
23
|
+
request.env[ENV_USER_ID_KEY]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def mbuzz_visitor_id
|
|
27
|
+
request.env[ENV_VISITOR_ID_KEY]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def mbuzz_session_id
|
|
31
|
+
request.env[ENV_SESSION_ID_KEY]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack"
|
|
4
|
+
|
|
5
|
+
module Mbuzz
|
|
6
|
+
module Middleware
|
|
7
|
+
class Tracking
|
|
8
|
+
attr_reader :app, :request
|
|
9
|
+
|
|
10
|
+
def initialize(app)
|
|
11
|
+
@app = app
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(env)
|
|
15
|
+
return app.call(env) if skip_request?(env)
|
|
16
|
+
|
|
17
|
+
reset_request_state!
|
|
18
|
+
@request = Rack::Request.new(env)
|
|
19
|
+
|
|
20
|
+
env[ENV_VISITOR_ID_KEY] = visitor_id
|
|
21
|
+
env[ENV_USER_ID_KEY] = user_id
|
|
22
|
+
env[ENV_SESSION_ID_KEY] = session_id
|
|
23
|
+
|
|
24
|
+
RequestContext.with_context(request: request) do
|
|
25
|
+
create_session_if_new
|
|
26
|
+
|
|
27
|
+
status, headers, body = app.call(env)
|
|
28
|
+
set_visitor_cookie(headers)
|
|
29
|
+
set_session_cookie(headers)
|
|
30
|
+
[status, headers, body]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Path filtering - skip health checks, static assets, etc.
|
|
35
|
+
|
|
36
|
+
def skip_request?(env)
|
|
37
|
+
path = env["PATH_INFO"].to_s.downcase
|
|
38
|
+
|
|
39
|
+
skip_by_path?(path) || skip_by_extension?(path)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def skip_by_path?(path)
|
|
43
|
+
Mbuzz.config.all_skip_paths.any? { |skip| path.start_with?(skip) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def skip_by_extension?(path)
|
|
47
|
+
Mbuzz.config.all_skip_extensions.any? { |ext| path.end_with?(ext) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def reset_request_state!
|
|
53
|
+
@request = nil
|
|
54
|
+
@visitor_id = nil
|
|
55
|
+
@session_id = nil
|
|
56
|
+
@user_id = nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def visitor_id
|
|
60
|
+
@visitor_id ||= visitor_id_from_cookie || Visitor::Identifier.generate
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def visitor_id_from_cookie
|
|
64
|
+
request.cookies[VISITOR_COOKIE_NAME]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def user_id
|
|
68
|
+
@user_id ||= user_id_from_session
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def user_id_from_session
|
|
72
|
+
request.session[SESSION_USER_ID_KEY] if request.session
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def set_visitor_cookie(headers)
|
|
76
|
+
Rack::Utils.set_cookie_header!(headers, VISITOR_COOKIE_NAME, visitor_cookie_options)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def visitor_cookie_options
|
|
80
|
+
base_cookie_options.merge(
|
|
81
|
+
value: visitor_id,
|
|
82
|
+
max_age: VISITOR_COOKIE_MAX_AGE
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Session ID management
|
|
87
|
+
|
|
88
|
+
def session_id
|
|
89
|
+
@session_id ||= session_id_from_cookie || generate_session_id
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def session_id_from_cookie
|
|
93
|
+
request.cookies[SESSION_COOKIE_NAME]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def generate_session_id
|
|
97
|
+
SecureRandom.hex(32)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def new_session?
|
|
101
|
+
session_id_from_cookie.nil?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Session creation
|
|
105
|
+
|
|
106
|
+
def create_session_if_new
|
|
107
|
+
return unless new_session?
|
|
108
|
+
|
|
109
|
+
create_session_async
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def create_session_async
|
|
113
|
+
Thread.new { create_session }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def create_session
|
|
117
|
+
Client.session(
|
|
118
|
+
visitor_id: visitor_id,
|
|
119
|
+
session_id: session_id,
|
|
120
|
+
url: request.url,
|
|
121
|
+
referrer: request.referer
|
|
122
|
+
)
|
|
123
|
+
rescue => e
|
|
124
|
+
log_session_error(e)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def log_session_error(error)
|
|
128
|
+
Mbuzz.config.logger&.error("Session creation failed: #{error.message}")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Session cookie
|
|
132
|
+
|
|
133
|
+
def set_session_cookie(headers)
|
|
134
|
+
Rack::Utils.set_cookie_header!(headers, SESSION_COOKIE_NAME, session_cookie_options)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def session_cookie_options
|
|
138
|
+
base_cookie_options.merge(
|
|
139
|
+
value: session_id,
|
|
140
|
+
max_age: SESSION_COOKIE_MAX_AGE
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Shared cookie options
|
|
145
|
+
|
|
146
|
+
def base_cookie_options
|
|
147
|
+
options = {
|
|
148
|
+
path: VISITOR_COOKIE_PATH,
|
|
149
|
+
httponly: true,
|
|
150
|
+
same_site: VISITOR_COOKIE_SAME_SITE
|
|
151
|
+
}
|
|
152
|
+
options[:secure] = true if request.ssl?
|
|
153
|
+
options
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
initializer "mbuzz.configure_rails" do |app|
|
|
6
|
+
app.middleware.use Mbuzz::Middleware::Tracking
|
|
7
|
+
|
|
8
|
+
ActiveSupport.on_load(:action_controller) do
|
|
9
|
+
include Mbuzz::ControllerHelpers
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
class RequestContext
|
|
5
|
+
def self.with_context(request:)
|
|
6
|
+
Thread.current[:mbuzz_request] = request
|
|
7
|
+
yield
|
|
8
|
+
ensure
|
|
9
|
+
Thread.current[:mbuzz_request] = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.current
|
|
13
|
+
return nil unless Thread.current[:mbuzz_request]
|
|
14
|
+
|
|
15
|
+
new(Thread.current[:mbuzz_request])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_reader :request
|
|
19
|
+
|
|
20
|
+
def initialize(request)
|
|
21
|
+
@request = request
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def url
|
|
25
|
+
@request.url
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def referrer
|
|
29
|
+
@request.referrer
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def user_agent
|
|
33
|
+
@request.user_agent
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def enriched_properties(custom = {})
|
|
37
|
+
{ url: url, referrer: referrer }.compact.merge(custom)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/mbuzz.rb
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "mbuzz/version"
|
|
4
|
+
require_relative "mbuzz/configuration"
|
|
5
|
+
require_relative "mbuzz/visitor/identifier"
|
|
6
|
+
require_relative "mbuzz/request_context"
|
|
7
|
+
require_relative "mbuzz/api"
|
|
8
|
+
require_relative "mbuzz/client"
|
|
9
|
+
require_relative "mbuzz/middleware/tracking"
|
|
10
|
+
require_relative "mbuzz/controller_helpers"
|
|
11
|
+
|
|
12
|
+
require_relative "mbuzz/railtie" if defined?(Rails::Railtie)
|
|
13
|
+
|
|
14
|
+
module Mbuzz
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
|
|
17
|
+
EVENTS_PATH = "/events"
|
|
18
|
+
IDENTIFY_PATH = "/identify"
|
|
19
|
+
CONVERSIONS_PATH = "/conversions"
|
|
20
|
+
SESSIONS_PATH = "/sessions"
|
|
21
|
+
|
|
22
|
+
VISITOR_COOKIE_NAME = "_mbuzz_vid"
|
|
23
|
+
VISITOR_COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 2 # 2 years
|
|
24
|
+
VISITOR_COOKIE_PATH = "/"
|
|
25
|
+
VISITOR_COOKIE_SAME_SITE = "Lax"
|
|
26
|
+
|
|
27
|
+
SESSION_COOKIE_NAME = "_mbuzz_sid"
|
|
28
|
+
SESSION_COOKIE_MAX_AGE = 30 * 60 # 30 minutes
|
|
29
|
+
|
|
30
|
+
SESSION_USER_ID_KEY = "user_id"
|
|
31
|
+
ENV_USER_ID_KEY = "mbuzz.user_id"
|
|
32
|
+
ENV_VISITOR_ID_KEY = "mbuzz.visitor_id"
|
|
33
|
+
ENV_SESSION_ID_KEY = "mbuzz.session_id"
|
|
34
|
+
|
|
35
|
+
# ============================================================================
|
|
36
|
+
# Configuration
|
|
37
|
+
# ============================================================================
|
|
38
|
+
|
|
39
|
+
def self.config
|
|
40
|
+
@config ||= Configuration.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# New simplified configuration method (v0.5.0)
|
|
44
|
+
# @param api_key [String] Your mbuzz API key
|
|
45
|
+
# @param api_url [String, nil] Override API URL (defaults to https://mbuzz.co/api/v1)
|
|
46
|
+
# @param session_timeout [Integer, nil] Session timeout in seconds
|
|
47
|
+
# @param debug [Boolean, nil] Enable debug logging
|
|
48
|
+
# @param skip_paths [Array<String>, nil] Additional paths to skip tracking (e.g., ["/admin", "/internal"])
|
|
49
|
+
# @param skip_extensions [Array<String>, nil] Additional extensions to skip (e.g., [".pdf"])
|
|
50
|
+
def self.init(api_key:, api_url: nil, session_timeout: nil, debug: nil, skip_paths: nil, skip_extensions: nil)
|
|
51
|
+
config.api_key = api_key
|
|
52
|
+
config.api_url = api_url if api_url
|
|
53
|
+
config.session_timeout = session_timeout if session_timeout
|
|
54
|
+
config.debug = debug unless debug.nil?
|
|
55
|
+
config.skip_paths = skip_paths if skip_paths
|
|
56
|
+
config.skip_extensions = skip_extensions if skip_extensions
|
|
57
|
+
config
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @deprecated Use {.init} instead
|
|
61
|
+
def self.configure
|
|
62
|
+
warn "[DEPRECATION] Mbuzz.configure is deprecated. Use Mbuzz.init(api_key: ...) instead."
|
|
63
|
+
yield(config)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ============================================================================
|
|
67
|
+
# Context Accessors
|
|
68
|
+
# ============================================================================
|
|
69
|
+
|
|
70
|
+
def self.visitor_id
|
|
71
|
+
RequestContext.current&.request&.env&.dig(ENV_VISITOR_ID_KEY) || fallback_visitor_id
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.fallback_visitor_id
|
|
75
|
+
@fallback_visitor_id ||= Visitor::Identifier.generate
|
|
76
|
+
end
|
|
77
|
+
private_class_method :fallback_visitor_id
|
|
78
|
+
|
|
79
|
+
def self.user_id
|
|
80
|
+
RequestContext.current&.request&.env&.dig(ENV_USER_ID_KEY)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.session_id
|
|
84
|
+
RequestContext.current&.request&.env&.dig(ENV_SESSION_ID_KEY)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# ============================================================================
|
|
88
|
+
# 4-Call Model API
|
|
89
|
+
# ============================================================================
|
|
90
|
+
|
|
91
|
+
# Track an event (journey step)
|
|
92
|
+
#
|
|
93
|
+
# @param event_type [String] The name of the event
|
|
94
|
+
# @param properties [Hash] Custom event properties (url, referrer auto-added)
|
|
95
|
+
# @return [Hash, false] Result hash on success, false on failure
|
|
96
|
+
#
|
|
97
|
+
# @example
|
|
98
|
+
# Mbuzz.event("add_to_cart", product_id: "SKU-123", price: 49.99)
|
|
99
|
+
#
|
|
100
|
+
def self.event(event_type, **properties)
|
|
101
|
+
Client.track(
|
|
102
|
+
visitor_id: visitor_id,
|
|
103
|
+
session_id: session_id,
|
|
104
|
+
user_id: user_id,
|
|
105
|
+
event_type: event_type,
|
|
106
|
+
properties: enriched_properties(properties)
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @deprecated Use {.event} instead
|
|
111
|
+
def self.track(event_type, properties: {})
|
|
112
|
+
warn "[DEPRECATION] Mbuzz.track is deprecated. Use Mbuzz.event(event_type, **properties) instead."
|
|
113
|
+
event(event_type, **properties)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Track a conversion (revenue-generating outcome)
|
|
117
|
+
#
|
|
118
|
+
# @param conversion_type [String] The type of conversion
|
|
119
|
+
# @param revenue [Numeric, nil] Revenue amount
|
|
120
|
+
# @param user_id [String, nil] User ID for acquisition-linked conversions
|
|
121
|
+
# @param is_acquisition [Boolean] Mark this as the acquisition conversion for this user
|
|
122
|
+
# @param inherit_acquisition [Boolean] Inherit attribution from user's acquisition conversion
|
|
123
|
+
# @param properties [Hash] Custom properties
|
|
124
|
+
# @return [Hash, false] Result hash on success, false on failure
|
|
125
|
+
#
|
|
126
|
+
# @example Basic conversion
|
|
127
|
+
# Mbuzz.conversion("purchase", revenue: 99.99, order_id: "ORD-123")
|
|
128
|
+
#
|
|
129
|
+
# @example Acquisition conversion (marks signup as THE acquisition moment)
|
|
130
|
+
# Mbuzz.conversion("signup", user_id: "user_123", is_acquisition: true)
|
|
131
|
+
#
|
|
132
|
+
# @example Recurring revenue (inherits attribution from acquisition)
|
|
133
|
+
# Mbuzz.conversion("payment", user_id: "user_123", revenue: 49.00, inherit_acquisition: true)
|
|
134
|
+
#
|
|
135
|
+
def self.conversion(conversion_type, revenue: nil, user_id: nil, is_acquisition: false, inherit_acquisition: false, **properties)
|
|
136
|
+
Client.conversion(
|
|
137
|
+
visitor_id: visitor_id,
|
|
138
|
+
user_id: user_id,
|
|
139
|
+
conversion_type: conversion_type,
|
|
140
|
+
revenue: revenue,
|
|
141
|
+
is_acquisition: is_acquisition,
|
|
142
|
+
inherit_acquisition: inherit_acquisition,
|
|
143
|
+
properties: enriched_properties(properties)
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Identify a user and optionally link to current visitor
|
|
148
|
+
#
|
|
149
|
+
# @param user_id [String, Numeric] Your application's user identifier
|
|
150
|
+
# @param traits [Hash] User attributes (email, name, plan, etc.)
|
|
151
|
+
# @param visitor_id [String, nil] Explicit visitor ID (auto-captured from cookie if nil)
|
|
152
|
+
# @return [Hash, false] Result hash with identity_id and visitor_linked, or false on failure
|
|
153
|
+
#
|
|
154
|
+
# @example Basic identification
|
|
155
|
+
# Mbuzz.identify("user_123", traits: { email: "jane@example.com" })
|
|
156
|
+
#
|
|
157
|
+
# @example With explicit visitor_id
|
|
158
|
+
# Mbuzz.identify("user_123", visitor_id: "abc123...", traits: { email: "jane@example.com" })
|
|
159
|
+
#
|
|
160
|
+
def self.identify(user_id, traits: {}, visitor_id: nil)
|
|
161
|
+
Client.identify(
|
|
162
|
+
user_id: user_id,
|
|
163
|
+
visitor_id: visitor_id || self.visitor_id,
|
|
164
|
+
traits: traits
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# ============================================================================
|
|
169
|
+
# Private Helpers
|
|
170
|
+
# ============================================================================
|
|
171
|
+
|
|
172
|
+
def self.enriched_properties(custom_properties)
|
|
173
|
+
return custom_properties unless RequestContext.current
|
|
174
|
+
|
|
175
|
+
RequestContext.current.enriched_properties(custom_properties)
|
|
176
|
+
end
|
|
177
|
+
private_class_method :enriched_properties
|
|
178
|
+
end
|