mbuzz 0.7.1 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb41261b9e19c46609f4e7a8867d73b5a902acecc3db3362b4a47e2cacefa9db
4
- data.tar.gz: ce644b10fa63055065960bb72a61eb53077d4bbe110eac40ed5bd6918d2487c1
3
+ metadata.gz: 0d8514076473bbc4fa543510e84789f8cb91ccc99e916b3cef4f4928aaf6baf5
4
+ data.tar.gz: 159074f0dbac29eb6b721bab4288ce976e4e4361ba503caffc36f37e79cf315e
5
5
  SHA512:
6
- metadata.gz: 03bed0944d6ee7286e4f002d05032c8ced19d013da1dae69d429046b1ebad510cae67510d48d9af53fac8a8832a1a1495657c6b671a16976fabb5eb8ec316f3d
7
- data.tar.gz: 4a0693caaa53219fbf4368c1b194722889c516e88eea6f7bfe781f7a44293659977e6691901f238f45ef66f08f367d674c2f22e6f8a63e22f3326368e6ae8f10
6
+ metadata.gz: 3d515f639e577a68d9daa8afe13de92dec0a5556cfad2e335ea482f7db0240ff996dd1fb6bbf3388e7f632cb8300e439028091cf1c551627c9be1c22e73ca666
7
+ data.tar.gz: 9fbe4999f2ce0855d83847d61b4684f30217c01c9074df4cced51e4083167cf3ca24e0e5223f2e57fd859aab6d88f60a68a0adc3074bd0a7f4356c0f9d3e5116
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mbuzz
4
+ class Client
5
+ class SessionRequest
6
+ # Response keys
7
+ STATUS_KEY = "status"
8
+ VISITOR_ID_KEY = "visitor_id"
9
+ SESSION_ID_KEY = "session_id"
10
+ CHANNEL_KEY = "channel"
11
+
12
+ # Response values
13
+ ACCEPTED_STATUS = "accepted"
14
+
15
+ def initialize(visitor_id:, session_id:, url:, referrer: nil, device_fingerprint: nil, started_at: nil)
16
+ @visitor_id = visitor_id
17
+ @session_id = session_id
18
+ @url = url
19
+ @referrer = referrer
20
+ @device_fingerprint = device_fingerprint
21
+ @started_at = started_at
22
+ end
23
+
24
+ def call
25
+ return false unless valid?
26
+
27
+ parse_response(response)
28
+ rescue StandardError
29
+ false
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :visitor_id, :session_id, :url, :referrer, :device_fingerprint, :started_at
35
+
36
+ def valid?
37
+ present?(visitor_id) && present?(session_id) && present?(url)
38
+ end
39
+
40
+ def response
41
+ @response ||= Api.post_with_response(SESSIONS_PATH, { session: payload })
42
+ end
43
+
44
+ def payload
45
+ {
46
+ visitor_id: visitor_id,
47
+ session_id: session_id,
48
+ url: url,
49
+ referrer: referrer,
50
+ device_fingerprint: device_fingerprint,
51
+ started_at: started_at || Time.now.utc.iso8601
52
+ }.compact
53
+ end
54
+
55
+ def parse_response(resp)
56
+ return false unless accepted?(resp)
57
+
58
+ {
59
+ success: true,
60
+ visitor_id: resp[VISITOR_ID_KEY],
61
+ session_id: resp[SESSION_ID_KEY],
62
+ channel: resp[CHANNEL_KEY]
63
+ }
64
+ end
65
+
66
+ def accepted?(resp)
67
+ resp && resp[STATUS_KEY] == ACCEPTED_STATUS
68
+ end
69
+
70
+ def present?(value)
71
+ value && !value.to_s.strip.empty?
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/mbuzz/client.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "client/track_request"
4
4
  require_relative "client/identify_request"
5
5
  require_relative "client/conversion_request"
6
+ require_relative "client/session_request"
6
7
 
7
8
  module Mbuzz
8
9
  class Client
@@ -30,5 +31,16 @@ module Mbuzz
30
31
  identifier: identifier
31
32
  ).call
32
33
  end
34
+
35
+ def self.session(visitor_id:, session_id:, url:, referrer: nil, device_fingerprint: nil, started_at: nil)
36
+ SessionRequest.new(
37
+ visitor_id: visitor_id,
38
+ session_id: session_id,
39
+ url: url,
40
+ referrer: referrer,
41
+ device_fingerprint: device_fingerprint,
42
+ started_at: started_at
43
+ ).call
44
+ end
33
45
  end
34
46
  end
data/lib/mbuzz/current.rb CHANGED
@@ -21,6 +21,7 @@ module Mbuzz
21
21
  #
22
22
  class Current < ActiveSupport::CurrentAttributes
23
23
  attribute :visitor_id
24
+ attribute :session_id
24
25
  attribute :user_id
25
26
  attribute :ip
26
27
  attribute :user_agent
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack"
4
+ require "digest"
5
+ require "securerandom"
4
6
 
5
7
  module Mbuzz
6
8
  module Middleware
@@ -16,13 +18,17 @@ module Mbuzz
16
18
  context = build_request_context(request)
17
19
 
18
20
  env[ENV_VISITOR_ID_KEY] = context[:visitor_id]
21
+ env[ENV_SESSION_ID_KEY] = context[:session_id]
19
22
  env[ENV_USER_ID_KEY] = context[:user_id]
20
23
 
21
24
  store_in_current_attributes(context, request)
22
25
 
26
+ create_session_async(context, request) if context[:new_session]
27
+
23
28
  RequestContext.with_context(request: request) do
24
29
  status, headers, body = @app.call(env)
25
30
  set_visitor_cookie(headers, context, request)
31
+ set_session_cookie(headers, context, request)
26
32
  [status, headers, body]
27
33
  ensure
28
34
  reset_current_attributes
@@ -49,10 +55,26 @@ module Mbuzz
49
55
 
50
56
  # Build all request-specific context as a frozen hash
51
57
  # This ensures thread-safety by using local variables only
58
+ # IMPORTANT: All values needed by async session creation must be captured here,
59
+ # NOT accessed from the request object in the background thread (see #create_session)
52
60
  def build_request_context(request)
61
+ existing_session_id = session_id_from_cookie(request)
62
+ new_session = existing_session_id.nil?
63
+ ip = extract_ip(request)
64
+ user_agent = request.user_agent.to_s
65
+
53
66
  {
54
67
  visitor_id: resolve_visitor_id(request),
55
- user_id: user_id_from_session(request)
68
+ session_id: existing_session_id || generate_session_id,
69
+ user_id: user_id_from_session(request),
70
+ new_session: new_session,
71
+ # Session creation data - captured here for thread-safety
72
+ # Background thread must NOT read from request object
73
+ url: request.url,
74
+ referrer: request.referer,
75
+ ip: ip,
76
+ user_agent: user_agent,
77
+ device_fingerprint: Digest::SHA256.hexdigest("#{ip}|#{user_agent}")[0, 32]
56
78
  }.freeze
57
79
  end
58
80
 
@@ -64,11 +86,47 @@ module Mbuzz
64
86
  request.cookies[VISITOR_COOKIE_NAME]
65
87
  end
66
88
 
89
+ def session_id_from_cookie(request)
90
+ request.cookies[SESSION_COOKIE_NAME]
91
+ end
92
+
93
+ def generate_session_id
94
+ SecureRandom.uuid
95
+ end
96
+
67
97
  def user_id_from_session(request)
68
98
  request.session[SESSION_USER_ID_KEY] if request.session
69
99
  end
70
100
 
71
- # Cookie setting - only visitor cookie (sessions are server-side)
101
+ # Session creation - async to not block request
102
+ # IMPORTANT: This runs in a background thread. All data must come from
103
+ # the context hash, NOT from the request object (which may be invalid)
104
+
105
+ def create_session_async(context, _request)
106
+ Thread.new do
107
+ create_session(context)
108
+ rescue StandardError => e
109
+ log_error("Session creation failed: #{e.message}") if Mbuzz.config.debug
110
+ end
111
+ end
112
+
113
+ def create_session(context)
114
+ Client.session(
115
+ visitor_id: context[:visitor_id],
116
+ session_id: context[:session_id],
117
+ url: context[:url],
118
+ referrer: context[:referrer],
119
+ device_fingerprint: context[:device_fingerprint]
120
+ )
121
+ end
122
+
123
+ def log_error(message)
124
+ return unless defined?(Rails) && Rails.logger
125
+
126
+ Rails.logger.error("[Mbuzz] #{message}")
127
+ end
128
+
129
+ # Cookie setting - visitor and session cookies
72
130
 
73
131
  def set_visitor_cookie(headers, context, request)
74
132
  Rack::Utils.set_cookie_header!(
@@ -78,6 +136,14 @@ module Mbuzz
78
136
  )
79
137
  end
80
138
 
139
+ def set_session_cookie(headers, context, request)
140
+ Rack::Utils.set_cookie_header!(
141
+ headers,
142
+ SESSION_COOKIE_NAME,
143
+ session_cookie_options(context, request)
144
+ )
145
+ end
146
+
81
147
  def visitor_cookie_options(context, request)
82
148
  base_cookie_options(request).merge(
83
149
  value: context[:visitor_id],
@@ -85,6 +151,13 @@ module Mbuzz
85
151
  )
86
152
  end
87
153
 
154
+ def session_cookie_options(context, request)
155
+ base_cookie_options(request).merge(
156
+ value: context[:session_id],
157
+ max_age: SESSION_COOKIE_MAX_AGE
158
+ )
159
+ end
160
+
88
161
  def base_cookie_options(request)
89
162
  options = {
90
163
  path: VISITOR_COOKIE_PATH,
@@ -100,6 +173,7 @@ module Mbuzz
100
173
  return unless defined?(Mbuzz::Current)
101
174
 
102
175
  Mbuzz::Current.visitor_id = context[:visitor_id]
176
+ Mbuzz::Current.session_id = context[:session_id]
103
177
  Mbuzz::Current.user_id = context[:user_id]
104
178
  Mbuzz::Current.ip = extract_ip(request)
105
179
  Mbuzz::Current.user_agent = request.user_agent
data/lib/mbuzz/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbuzz
4
- VERSION = "0.7.1"
4
+ VERSION = "0.7.2"
5
5
  end
data/lib/mbuzz.rb CHANGED
@@ -20,15 +20,20 @@ module Mbuzz
20
20
  EVENTS_PATH = "/events"
21
21
  IDENTIFY_PATH = "/identify"
22
22
  CONVERSIONS_PATH = "/conversions"
23
+ SESSIONS_PATH = "/sessions"
23
24
 
24
25
  VISITOR_COOKIE_NAME = "_mbuzz_vid"
25
26
  VISITOR_COOKIE_MAX_AGE = 60 * 60 * 24 * 365 * 2 # 2 years
26
27
  VISITOR_COOKIE_PATH = "/"
27
28
  VISITOR_COOKIE_SAME_SITE = "Lax"
28
29
 
30
+ SESSION_COOKIE_NAME = "_mbuzz_sid"
31
+ SESSION_COOKIE_MAX_AGE = 30 * 60 # 30 minutes
32
+
29
33
  SESSION_USER_ID_KEY = "user_id"
30
34
  ENV_USER_ID_KEY = "mbuzz.user_id"
31
35
  ENV_VISITOR_ID_KEY = "mbuzz.visitor_id"
36
+ ENV_SESSION_ID_KEY = "mbuzz.session_id"
32
37
 
33
38
  # ============================================================================
34
39
  # Configuration
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbuzz
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - mbuzz team
@@ -44,6 +44,7 @@ files:
44
44
  - lib/mbuzz/client.rb
45
45
  - lib/mbuzz/client/conversion_request.rb
46
46
  - lib/mbuzz/client/identify_request.rb
47
+ - lib/mbuzz/client/session_request.rb
47
48
  - lib/mbuzz/client/track_request.rb
48
49
  - lib/mbuzz/configuration.rb
49
50
  - lib/mbuzz/controller_helpers.rb