mbuzz 0.7.0 → 0.7.1

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: 0fa7c025330829c88ab9a75a22ca286d14adc1df7872295c5519df4c89536db9
4
- data.tar.gz: 62d6675e5ffe9504e2262f84551c286366acc2ad42b6ca6d224367902d9ec3fb
3
+ metadata.gz: eb41261b9e19c46609f4e7a8867d73b5a902acecc3db3362b4a47e2cacefa9db
4
+ data.tar.gz: ce644b10fa63055065960bb72a61eb53077d4bbe110eac40ed5bd6918d2487c1
5
5
  SHA512:
6
- metadata.gz: eb90968b0d4c17bdc7c5019fa0058c257233bc00e1921f0b70a3dbf885fd8aecd1f9d80ab125d31ab535be5e55a44ecc2e0d46e9cb9c51fef4b9305634241b2f
7
- data.tar.gz: cef7cbbc7817a6d7c4eca635594ac9e3b9661d81b33cc7d956376cb9fd763a6d216651e6ec499146e31dca042f024216f3c85c08c6d83f4f31dfb1fff64e8b56
6
+ metadata.gz: 03bed0944d6ee7286e4f002d05032c8ced19d013da1dae69d429046b1ebad510cae67510d48d9af53fac8a8832a1a1495657c6b671a16976fabb5eb8ec316f3d
7
+ data.tar.gz: 4a0693caaa53219fbf4368c1b194722889c516e88eea6f7bfe781f7a44293659977e6691901f238f45ef66f08f367d674c2f22e6f8a63e22f3326368e6ae8f10
data/README.md CHANGED
@@ -121,6 +121,63 @@ Mbuzz.user_id # Current user ID (from session["user_id"])
121
121
  Mbuzz.session_id # Current session ID (from cookie)
122
122
  ```
123
123
 
124
+ ## Background Jobs
125
+
126
+ When tracking from background jobs (Sidekiq, GoodJob, etc.), there's no HTTP request context. Rails 7+ handles this automatically via `CurrentAttributes`.
127
+
128
+ ### Rails 7+ (Automatic)
129
+
130
+ mbuzz uses `ActiveSupport::CurrentAttributes` which Rails automatically serializes into ActiveJob payloads:
131
+
132
+ ```ruby
133
+ # In your controller - just enqueue the job
134
+ class OrdersController < ApplicationController
135
+ def create
136
+ @order = Order.create!(order_params)
137
+ ProcessOrderJob.perform_later(@order.id)
138
+ # visitor_id is automatically captured and passed to the job
139
+ end
140
+ end
141
+
142
+ # In your job - mbuzz just works!
143
+ class ProcessOrderJob < ApplicationJob
144
+ def perform(order_id)
145
+ order = Order.find(order_id)
146
+ # Mbuzz::Current.visitor_id was restored by Rails
147
+ Mbuzz.conversion("purchase", revenue: order.total)
148
+ end
149
+ end
150
+ ```
151
+
152
+ **How it works:**
153
+ 1. Middleware captures `visitor_id` from cookie into `Mbuzz::Current`
154
+ 2. Controller enqueues job
155
+ 3. Rails serializes `Mbuzz::Current` into job payload
156
+ 4. Job runs → Rails restores `Mbuzz::Current`
157
+ 5. `Mbuzz.conversion()` reads from `Current` - works!
158
+
159
+ ### Alternative: Explicit visitor_id
160
+
161
+ For non-Rails apps or when you need more control:
162
+
163
+ ```ruby
164
+ # Store visitor_id on your model
165
+ class Order < ApplicationRecord
166
+ before_create { self.mbuzz_visitor_id = Mbuzz.visitor_id }
167
+ end
168
+
169
+ # Pass explicitly in background job
170
+ class ProcessOrderJob
171
+ def perform(order_id)
172
+ order = Order.find(order_id)
173
+ Mbuzz.conversion("purchase",
174
+ visitor_id: order.mbuzz_visitor_id, # Explicit
175
+ revenue: order.total
176
+ )
177
+ end
178
+ end
179
+ ```
180
+
124
181
  ## Rack / Sinatra Integration
125
182
 
126
183
  For non-Rails apps, add the middleware manually:
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mbuzz
4
+ # CurrentAttributes for automatic background job context propagation.
5
+ #
6
+ # Rails automatically serializes CurrentAttributes into ActiveJob payloads
7
+ # and restores them when jobs execute. This means visitor_id captured during
8
+ # the original request is available in background jobs without any manual
9
+ # passing or database storage.
10
+ #
11
+ # How it works:
12
+ # 1. Middleware captures visitor_id from cookie
13
+ # 2. Stores in Mbuzz::Current.visitor_id
14
+ # 3. Controller enqueues background job
15
+ # 4. Rails serializes Current attributes into job payload
16
+ # 5. Job runs on different thread/process
17
+ # 6. Rails restores Current.visitor_id before job executes
18
+ # 7. Mbuzz.event/conversion reads from Current.visitor_id
19
+ #
20
+ # This is why customers don't need to store visitor_id in their database.
21
+ #
22
+ class Current < ActiveSupport::CurrentAttributes
23
+ attribute :visitor_id
24
+ attribute :user_id
25
+ attribute :ip
26
+ attribute :user_agent
27
+ end
28
+ end
@@ -18,10 +18,14 @@ module Mbuzz
18
18
  env[ENV_VISITOR_ID_KEY] = context[:visitor_id]
19
19
  env[ENV_USER_ID_KEY] = context[:user_id]
20
20
 
21
+ store_in_current_attributes(context, request)
22
+
21
23
  RequestContext.with_context(request: request) do
22
24
  status, headers, body = @app.call(env)
23
25
  set_visitor_cookie(headers, context, request)
24
26
  [status, headers, body]
27
+ ensure
28
+ reset_current_attributes
25
29
  end
26
30
  end
27
31
 
@@ -90,6 +94,29 @@ module Mbuzz
90
94
  options[:secure] = true if request.ssl?
91
95
  options
92
96
  end
97
+
98
+ # Store context in CurrentAttributes for background job propagation
99
+ def store_in_current_attributes(context, request)
100
+ return unless defined?(Mbuzz::Current)
101
+
102
+ Mbuzz::Current.visitor_id = context[:visitor_id]
103
+ Mbuzz::Current.user_id = context[:user_id]
104
+ Mbuzz::Current.ip = extract_ip(request)
105
+ Mbuzz::Current.user_agent = request.user_agent
106
+ end
107
+
108
+ def reset_current_attributes
109
+ return unless defined?(Mbuzz::Current)
110
+
111
+ Mbuzz::Current.reset
112
+ end
113
+
114
+ def extract_ip(request)
115
+ forwarded = request.env["HTTP_X_FORWARDED_FOR"]
116
+ return forwarded.split(",").first.strip if forwarded
117
+
118
+ request.ip
119
+ end
93
120
  end
94
121
  end
95
122
  end
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.0"
4
+ VERSION = "0.7.1"
5
5
  end
data/lib/mbuzz.rb CHANGED
@@ -9,6 +9,9 @@ require_relative "mbuzz/client"
9
9
  require_relative "mbuzz/middleware/tracking"
10
10
  require_relative "mbuzz/controller_helpers"
11
11
 
12
+ # CurrentAttributes for automatic background job context propagation (Rails only)
13
+ require_relative "mbuzz/current" if defined?(ActiveSupport::CurrentAttributes)
14
+
12
15
  require_relative "mbuzz/railtie" if defined?(Rails::Railtie)
13
16
 
14
17
  module Mbuzz
@@ -62,18 +65,25 @@ module Mbuzz
62
65
  # Context Accessors
63
66
  # ============================================================================
64
67
 
68
+ # Returns visitor_id from Current (background jobs) or request context
65
69
  def self.visitor_id
66
- RequestContext.current&.request&.env&.dig(ENV_VISITOR_ID_KEY) || fallback_visitor_id
70
+ current_visitor_id || RequestContext.current&.request&.env&.dig(ENV_VISITOR_ID_KEY)
67
71
  end
68
72
 
69
- def self.fallback_visitor_id
70
- @fallback_visitor_id ||= Visitor::Identifier.generate
73
+ def self.user_id
74
+ current_user_id || RequestContext.current&.request&.env&.dig(ENV_USER_ID_KEY)
71
75
  end
72
- private_class_method :fallback_visitor_id
73
76
 
74
- def self.user_id
75
- RequestContext.current&.request&.env&.dig(ENV_USER_ID_KEY)
77
+ # Check Current attributes (for background job support)
78
+ def self.current_visitor_id
79
+ defined?(Current) ? Current.visitor_id : nil
80
+ end
81
+ private_class_method :current_visitor_id
82
+
83
+ def self.current_user_id
84
+ defined?(Current) ? Current.user_id : nil
76
85
  end
86
+ private_class_method :current_user_id
77
87
 
78
88
  # ============================================================================
79
89
  # 4-Call Model API
@@ -82,20 +92,30 @@ module Mbuzz
82
92
  # Track an event (journey step)
83
93
  #
84
94
  # @param event_type [String] The name of the event
95
+ # @param visitor_id [String, nil] Explicit visitor ID (required for background jobs)
85
96
  # @param properties [Hash] Custom event properties (url, referrer auto-added)
86
97
  # @param identifier [Hash, nil] Optional identifier for cross-device identity resolution
87
98
  # @return [Hash, false] Result hash on success, false on failure
88
99
  #
89
- # @example
100
+ # @example Normal usage (within request context)
90
101
  # Mbuzz.event("add_to_cart", product_id: "SKU-123", price: 49.99)
91
102
  #
103
+ # @example Background job (must pass explicit visitor_id)
104
+ # Mbuzz.event("order_processed", visitor_id: order.mbuzz_visitor_id, order_id: order.id)
105
+ #
92
106
  # @example With identifier for cross-device tracking
93
107
  # Mbuzz.event("page_view", identifier: { email: "user@example.com" })
94
108
  #
95
- def self.event(event_type, identifier: nil, **properties)
109
+ def self.event(event_type, visitor_id: nil, identifier: nil, **properties)
110
+ resolved_visitor_id = visitor_id || self.visitor_id
111
+ resolved_user_id = user_id
112
+
113
+ # Must have at least one identifier
114
+ return false unless resolved_visitor_id || resolved_user_id
115
+
96
116
  Client.track(
97
- visitor_id: visitor_id,
98
- user_id: user_id,
117
+ visitor_id: resolved_visitor_id,
118
+ user_id: resolved_user_id,
99
119
  event_type: event_type,
100
120
  properties: enriched_properties(properties),
101
121
  ip: current_ip,
@@ -113,6 +133,7 @@ module Mbuzz
113
133
  # Track a conversion (revenue-generating outcome)
114
134
  #
115
135
  # @param conversion_type [String] The type of conversion
136
+ # @param visitor_id [String, nil] Explicit visitor ID (required for background jobs)
116
137
  # @param revenue [Numeric, nil] Revenue amount
117
138
  # @param user_id [String, nil] User ID for acquisition-linked conversions
118
139
  # @param is_acquisition [Boolean] Mark this as the acquisition conversion for this user
@@ -121,9 +142,12 @@ module Mbuzz
121
142
  # @param properties [Hash] Custom properties
122
143
  # @return [Hash, false] Result hash on success, false on failure
123
144
  #
124
- # @example Basic conversion
145
+ # @example Basic conversion (within request context)
125
146
  # Mbuzz.conversion("purchase", revenue: 99.99, order_id: "ORD-123")
126
147
  #
148
+ # @example Background job (must pass explicit visitor_id)
149
+ # Mbuzz.conversion("purchase", visitor_id: order.mbuzz_visitor_id, revenue: 99.99)
150
+ #
127
151
  # @example Acquisition conversion (marks signup as THE acquisition moment)
128
152
  # Mbuzz.conversion("signup", user_id: "user_123", is_acquisition: true)
129
153
  #
@@ -133,9 +157,14 @@ module Mbuzz
133
157
  # @example With identifier for cross-device tracking
134
158
  # Mbuzz.conversion("purchase", identifier: { email: "user@example.com" })
135
159
  #
136
- def self.conversion(conversion_type, revenue: nil, user_id: nil, is_acquisition: false, inherit_acquisition: false, identifier: nil, **properties)
160
+ def self.conversion(conversion_type, visitor_id: nil, revenue: nil, user_id: nil, is_acquisition: false, inherit_acquisition: false, identifier: nil, **properties)
161
+ resolved_visitor_id = visitor_id || self.visitor_id
162
+
163
+ # Must have at least one identifier (visitor_id or user_id)
164
+ return false unless resolved_visitor_id || user_id
165
+
137
166
  Client.conversion(
138
- visitor_id: visitor_id,
167
+ visitor_id: resolved_visitor_id,
139
168
  user_id: user_id,
140
169
  conversion_type: conversion_type,
141
170
  revenue: revenue,
@@ -181,12 +210,26 @@ module Mbuzz
181
210
  private_class_method :enriched_properties
182
211
 
183
212
  def self.current_ip
184
- RequestContext.current&.ip
213
+ current_attributes_ip || RequestContext.current&.ip
185
214
  end
186
215
  private_class_method :current_ip
187
216
 
188
217
  def self.current_user_agent
189
- RequestContext.current&.user_agent
218
+ current_attributes_user_agent || RequestContext.current&.user_agent
190
219
  end
191
220
  private_class_method :current_user_agent
221
+
222
+ def self.current_attributes_ip
223
+ return nil unless defined?(Current)
224
+
225
+ Current.ip
226
+ end
227
+ private_class_method :current_attributes_ip
228
+
229
+ def self.current_attributes_user_agent
230
+ return nil unless defined?(Current)
231
+
232
+ Current.user_agent
233
+ end
234
+ private_class_method :current_attributes_user_agent
192
235
  end
@@ -1,6 +1,13 @@
1
1
  # mbuzz SDK v0.7.0 - Deterministic Session IDs
2
2
 
3
- **Status**: Proposed
3
+ > **⚠️ SUPERSEDED**: This spec has been replaced by server-side session resolution.
4
+ > See: `multibuzz/lib/specs/1_visitor_session_tracking_spec.md`
5
+ >
6
+ > **What changed**: Instead of SDKs generating deterministic session IDs using time-buckets,
7
+ > the server now handles all session resolution using IP + User-Agent fingerprinting with
8
+ > a true 30-minute sliding window. SDKs no longer manage session cookies.
9
+
10
+ **Status**: SUPERSEDED (2026-01-09)
4
11
  **Last Updated**: 2025-12-29
5
12
  **Breaking Change**: No (backward compatible)
6
13
  **Affects**: All SDKs (Ruby, Python, PHP, Node)
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.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - mbuzz team
@@ -47,6 +47,7 @@ files:
47
47
  - lib/mbuzz/client/track_request.rb
48
48
  - lib/mbuzz/configuration.rb
49
49
  - lib/mbuzz/controller_helpers.rb
50
+ - lib/mbuzz/current.rb
50
51
  - lib/mbuzz/middleware/tracking.rb
51
52
  - lib/mbuzz/railtie.rb
52
53
  - lib/mbuzz/request_context.rb