mbuzz 0.9.0 → 0.10.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 +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +14 -8
- data/lib/mbuzz/client/session_request.rb +11 -2
- data/lib/mbuzz/client.rb +4 -2
- data/lib/mbuzz/middleware/session_endpoint.rb +2 -1
- data/lib/mbuzz/middleware/tracking.rb +14 -36
- data/lib/mbuzz/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 71d8b00de3facb7fd1290c03f1493edf09586333ad6ea44e1de03ebba375805f
|
|
4
|
+
data.tar.gz: 69eac0945b4b2630038753770fd398154cc1d407e15e9e8ff0d1770fb53bf95d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5f0a99184a1860342c7088c1642495d11db115ecbd7571bfffd6f29ea8fc770d6d0ea3d473f1ae7a6681eadb19d8b387ce86968df369d5e785301a1f157b2ad3
|
|
7
|
+
data.tar.gz: 3ba3d4419fa579e5840a46e398202637f4d7f5b119fbc2815b1cac5db1504f4d0f69a5d68481ad1075bb875d42e0121aa9f06696bf9cc9a254accb9c7a1a0aab
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.10.1] - 2026-09-17
|
|
9
|
+
|
|
10
|
+
### Fixed — the visitor's IP now reaches mbuzz on session create
|
|
11
|
+
|
|
12
|
+
- **`POST /api/v1/sessions` now carries `ip`.** The middleware already extracted the visitor's
|
|
13
|
+
address — it is half of `device_fingerprint = SHA256(ip|user_agent)` — and then dropped it at
|
|
14
|
+
three points on the way out: `Client.session` had no `ip:` keyword, `SessionRequest#payload`
|
|
15
|
+
omitted it, and `Middleware::Tracking`'s own call site omitted it too. Events and conversions have
|
|
16
|
+
always sent one; only sessions did not.
|
|
17
|
+
- **Why it matters.** mbuzz sends Meta's Conversions API `client_ip_address`, which Meta documents
|
|
18
|
+
as **required** for website events, and a session with no address means the events resolved
|
|
19
|
+
through it carry none either. Upgrading improves Event Match Quality for anyone using conversion
|
|
20
|
+
feedback; it changes nothing else.
|
|
21
|
+
- **No API change.** `ip:` is an optional keyword on `Client.session` and both middlewares fill it
|
|
22
|
+
automatically. Existing callers need no edit.
|
|
23
|
+
|
|
24
|
+
Found by the SDK integration harness rather than by unit tests: every layer was correct on its own,
|
|
25
|
+
and only a real request through a real app could tell the visitor's address from the app server's.
|
|
26
|
+
|
|
27
|
+
## [0.10.0] - 2026-09-08
|
|
28
|
+
|
|
29
|
+
### BREAKING — the page snippet is now required
|
|
30
|
+
|
|
31
|
+
- **A page response no longer sets the visitor cookie.** Only `POST /_mbuzz/session` mints, and the inline snippet in the README's "Full-page caching" section is what calls it. **Upgrading without adding that snippet stops tracking entirely** — no cookie is ever minted, so no visitor exists and every event is dropped.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **A cached page no longer hands every visitor the same id.** The page response carried a `Set-Cookie`, and a full-page cache stored it and replayed it to everyone — so unrelated people merged into a single journey. Corruption rather than loss: every row still exists, each attributed to the wrong person, and nothing looks missing. The page path now uses only the cookie the browser already holds; the uncached session endpoint is the sole place a visitor is minted.
|
|
36
|
+
|
|
37
|
+
Found by the cache harness at `sdk_integration_tests/scenarios/page_cache_test.rb`, and it could not have been found any other way: in isolation "reuse the cookie the browser presents" is correct — it is what lets a returning visitor keep their id. The bug exists only once a cache has handed one cookie to many people. The WordPress plugin reached the same conclusion first (`CookieBootstrap::CONTEXT_PAGE`).
|
|
38
|
+
|
|
8
39
|
## [0.9.0] - 2026-09-02
|
|
9
40
|
|
|
10
41
|
### Added
|
data/README.md
CHANGED
|
@@ -193,16 +193,22 @@ use Mbuzz::Middleware::Tracking
|
|
|
193
193
|
run MyApp
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
## Full-page caching
|
|
196
|
+
## Full-page caching — the snippet below is REQUIRED
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
never runs, no visitor cookie is set, and every later event is dropped for having no one to
|
|
201
|
-
attribute it to. The page renders perfectly and nothing is logged — the failure is silent.
|
|
198
|
+
**Add this to your layout or nothing is tracked.** Since 0.10.0 a page response never sets the
|
|
199
|
+
visitor cookie: only `POST /_mbuzz/session` mints, and the snippet is what calls it.
|
|
202
200
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
A page response can be stored by a full-page cache (Cloudflare, Varnish, nginx, Rack::Cache, a
|
|
202
|
+
CDN) and replayed to every visitor. A `Set-Cookie` sitting in that cache hands everyone the
|
|
203
|
+
*first* visitor's id, so unrelated people merge into one journey — corruption rather than loss,
|
|
204
|
+
since every row exists and is simply attributed to the wrong person. Minting only on a response
|
|
205
|
+
no cache stores is the only way to prevent it.
|
|
206
|
+
|
|
207
|
+
The same endpoint solves the original problem too: a cached page never enters the Rack stack,
|
|
208
|
+
so the tracking middleware cannot run — but this one request always reaches it.
|
|
209
|
+
|
|
210
|
+
`Mbuzz::Middleware::SessionEndpoint` answers `POST /_mbuzz/session`. Rails mounts it for you;
|
|
211
|
+
Rack and Sinatra apps add it ahead of `Tracking` as shown above.
|
|
206
212
|
|
|
207
213
|
Then call it once per page, from your layout:
|
|
208
214
|
|
|
@@ -12,7 +12,8 @@ module Mbuzz
|
|
|
12
12
|
# Response values
|
|
13
13
|
ACCEPTED_STATUS = "accepted"
|
|
14
14
|
|
|
15
|
-
def initialize(visitor_id:, session_id:, url:, referrer: nil, device_fingerprint: nil,
|
|
15
|
+
def initialize(visitor_id:, session_id:, url:, referrer: nil, device_fingerprint: nil,
|
|
16
|
+
user_agent: nil, started_at: nil, ip: nil)
|
|
16
17
|
@visitor_id = visitor_id
|
|
17
18
|
@session_id = session_id
|
|
18
19
|
@url = url
|
|
@@ -20,6 +21,7 @@ module Mbuzz
|
|
|
20
21
|
@device_fingerprint = device_fingerprint
|
|
21
22
|
@user_agent = user_agent
|
|
22
23
|
@started_at = started_at
|
|
24
|
+
@ip = ip
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def call
|
|
@@ -32,7 +34,8 @@ module Mbuzz
|
|
|
32
34
|
|
|
33
35
|
private
|
|
34
36
|
|
|
35
|
-
attr_reader :visitor_id, :session_id, :url, :referrer, :device_fingerprint, :user_agent,
|
|
37
|
+
attr_reader :visitor_id, :session_id, :url, :referrer, :device_fingerprint, :user_agent,
|
|
38
|
+
:started_at, :ip
|
|
36
39
|
|
|
37
40
|
def valid?
|
|
38
41
|
present?(visitor_id) && present?(session_id) && present?(url)
|
|
@@ -42,6 +45,11 @@ module Mbuzz
|
|
|
42
45
|
@response ||= Api.post_with_response(SESSIONS_PATH, { session: payload })
|
|
43
46
|
end
|
|
44
47
|
|
|
48
|
+
# `ip` is the visitor's address, which the middleware already extracted to
|
|
49
|
+
# compute `device_fingerprint` (SHA256(ip|user_agent)). It was computed
|
|
50
|
+
# and then dropped here until 2026-09-17: mbuzz needs it for Meta's
|
|
51
|
+
# `client_ip_address`, which Meta documents as required for website
|
|
52
|
+
# events, and the events/conversions payloads have always carried it.
|
|
45
53
|
def payload
|
|
46
54
|
{
|
|
47
55
|
visitor_id: visitor_id,
|
|
@@ -50,6 +58,7 @@ module Mbuzz
|
|
|
50
58
|
referrer: referrer,
|
|
51
59
|
device_fingerprint: device_fingerprint,
|
|
52
60
|
user_agent: user_agent,
|
|
61
|
+
ip: ip,
|
|
53
62
|
started_at: started_at || Time.now.utc.iso8601
|
|
54
63
|
}.compact
|
|
55
64
|
end
|
data/lib/mbuzz/client.rb
CHANGED
|
@@ -32,7 +32,8 @@ module Mbuzz
|
|
|
32
32
|
).call
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
def self.session(visitor_id:, session_id:, url:, referrer: nil, device_fingerprint: nil,
|
|
35
|
+
def self.session(visitor_id:, session_id:, url:, referrer: nil, device_fingerprint: nil,
|
|
36
|
+
user_agent: nil, started_at: nil, ip: nil)
|
|
36
37
|
SessionRequest.new(
|
|
37
38
|
visitor_id: visitor_id,
|
|
38
39
|
session_id: session_id,
|
|
@@ -40,7 +41,8 @@ module Mbuzz
|
|
|
40
41
|
referrer: referrer,
|
|
41
42
|
device_fingerprint: device_fingerprint,
|
|
42
43
|
user_agent: user_agent,
|
|
43
|
-
started_at: started_at
|
|
44
|
+
started_at: started_at,
|
|
45
|
+
ip: ip
|
|
44
46
|
).call
|
|
45
47
|
end
|
|
46
48
|
end
|
|
@@ -93,7 +93,8 @@ module Mbuzz
|
|
|
93
93
|
url: context[:url],
|
|
94
94
|
referrer: context[:referrer],
|
|
95
95
|
device_fingerprint: context[:device_fingerprint],
|
|
96
|
-
user_agent: context[:user_agent]
|
|
96
|
+
user_agent: context[:user_agent],
|
|
97
|
+
ip: context[:ip]
|
|
97
98
|
)
|
|
98
99
|
rescue StandardError => e
|
|
99
100
|
log_error("Session creation failed: #{e.message}") if Mbuzz.config.debug
|
|
@@ -15,6 +15,16 @@ module Mbuzz
|
|
|
15
15
|
return @app.call(env) if skip_request?(env)
|
|
16
16
|
|
|
17
17
|
request = Rack::Request.new(env)
|
|
18
|
+
|
|
19
|
+
# Only a visitor the browser already holds. This response may be stored
|
|
20
|
+
# by a full-page cache and replayed to everyone, so minting here would
|
|
21
|
+
# put a Set-Cookie in that cache and hand every later visitor the same
|
|
22
|
+
# id — unrelated people merged into one journey. Corruption, not loss:
|
|
23
|
+
# every row still exists, each attributed to the wrong person, and
|
|
24
|
+
# nothing looks missing. A first-time visitor is established a moment
|
|
25
|
+
# later by Middleware::SessionEndpoint, whose POST no cache stores.
|
|
26
|
+
return @app.call(env) unless visitor_id_from_cookie(request)
|
|
27
|
+
|
|
18
28
|
context = build_request_context(request)
|
|
19
29
|
|
|
20
30
|
env[ENV_VISITOR_ID_KEY] = context[:visitor_id]
|
|
@@ -26,9 +36,7 @@ module Mbuzz
|
|
|
26
36
|
create_session_async(context, request) if should_create_session?(env)
|
|
27
37
|
|
|
28
38
|
RequestContext.with_context(request: request) do
|
|
29
|
-
|
|
30
|
-
set_visitor_cookie(headers, context, request)
|
|
31
|
-
[status, headers, body]
|
|
39
|
+
@app.call(env)
|
|
32
40
|
ensure
|
|
33
41
|
reset_current_attributes
|
|
34
42
|
end
|
|
@@ -87,7 +95,7 @@ module Mbuzz
|
|
|
87
95
|
user_agent = request.user_agent.to_s
|
|
88
96
|
|
|
89
97
|
{
|
|
90
|
-
visitor_id:
|
|
98
|
+
visitor_id: visitor_id_from_cookie(request),
|
|
91
99
|
session_id: SecureRandom.uuid,
|
|
92
100
|
user_id: user_id_from_session(request),
|
|
93
101
|
url: request.url,
|
|
@@ -98,10 +106,6 @@ module Mbuzz
|
|
|
98
106
|
}.freeze
|
|
99
107
|
end
|
|
100
108
|
|
|
101
|
-
def resolve_visitor_id(request)
|
|
102
|
-
visitor_id_from_cookie(request) || Visitor::Identifier.generate
|
|
103
|
-
end
|
|
104
|
-
|
|
105
109
|
def visitor_id_from_cookie(request)
|
|
106
110
|
request.cookies[VISITOR_COOKIE_NAME]
|
|
107
111
|
end
|
|
@@ -129,7 +133,8 @@ module Mbuzz
|
|
|
129
133
|
url: context[:url],
|
|
130
134
|
referrer: context[:referrer],
|
|
131
135
|
device_fingerprint: context[:device_fingerprint],
|
|
132
|
-
user_agent: context[:user_agent]
|
|
136
|
+
user_agent: context[:user_agent],
|
|
137
|
+
ip: context[:ip]
|
|
133
138
|
)
|
|
134
139
|
end
|
|
135
140
|
|
|
@@ -139,33 +144,6 @@ module Mbuzz
|
|
|
139
144
|
Rails.logger.error("[Mbuzz] #{message}")
|
|
140
145
|
end
|
|
141
146
|
|
|
142
|
-
# Cookie setting - visitor identity only (sessions are server-side)
|
|
143
|
-
|
|
144
|
-
def set_visitor_cookie(headers, context, request)
|
|
145
|
-
Rack::Utils.set_cookie_header!(
|
|
146
|
-
headers,
|
|
147
|
-
VISITOR_COOKIE_NAME,
|
|
148
|
-
visitor_cookie_options(context, request)
|
|
149
|
-
)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def visitor_cookie_options(context, request)
|
|
153
|
-
base_cookie_options(request).merge(
|
|
154
|
-
value: context[:visitor_id],
|
|
155
|
-
max_age: VISITOR_COOKIE_MAX_AGE
|
|
156
|
-
)
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def base_cookie_options(request)
|
|
160
|
-
options = {
|
|
161
|
-
path: VISITOR_COOKIE_PATH,
|
|
162
|
-
httponly: true,
|
|
163
|
-
same_site: VISITOR_COOKIE_SAME_SITE
|
|
164
|
-
}
|
|
165
|
-
options[:secure] = true if request.ssl?
|
|
166
|
-
options
|
|
167
|
-
end
|
|
168
|
-
|
|
169
147
|
# Store context in CurrentAttributes for background job propagation
|
|
170
148
|
def store_in_current_attributes(context, request)
|
|
171
149
|
return unless defined?(Mbuzz::Current)
|
data/lib/mbuzz/version.rb
CHANGED