mbuzz 0.8.2 → 0.10.0
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 +24 -0
- data/README.md +43 -0
- data/lib/mbuzz/client/conversion_request.rb +6 -1
- data/lib/mbuzz/client/track_request.rb +8 -1
- data/lib/mbuzz/dropped_call.rb +41 -0
- data/lib/mbuzz/middleware/session_endpoint.rb +135 -0
- data/lib/mbuzz/middleware/tracking.rb +12 -35
- data/lib/mbuzz/railtie.rb +3 -0
- data/lib/mbuzz/version.rb +1 -1
- data/lib/mbuzz.rb +19 -4
- metadata +3 -11
- data/.DS_Store +0 -0
- data/CHECK_BUG.md +0 -168
- data/lib/.DS_Store +0 -0
- data/lib/specs/old/SPECIFICATION.md +0 -695
- data/lib/specs/old/conversions.md +0 -585
- data/lib/specs/old/event_ids_response.md +0 -346
- data/lib/specs/old/v0.2.0_breaking_changes.md +0 -519
- data/lib/specs/old/v2.0.0_sessions_upgrade.md +0 -265
- data/lib/specs/v0.5.0_four_call_model.md +0 -505
- data/lib/specs/v0.7.0_deterministic_sessions.md +0 -647
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03aa54a68ab1b6e6313d69c910c88aec7241e19d65b72b9cee37a6e187454692
|
|
4
|
+
data.tar.gz: 3ddce95a2f8795635278785f403daf6a41e15bc31fc01c3990989c20911ebc21
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ba8606ecbeec460a9145937b8d123c93c455476d92795e9ddf2b291082e57cdce4818cd709a427ed28d125e0ee6766110f0425039c43bfba75328f5da6f756f
|
|
7
|
+
data.tar.gz: 8d9233587278aaccb19fd5f124e486e1385b626b2eb4db3d7125ab0e64d6b1500117a33fe89e1fad84c846e6a292e6183760013ecb33e16d44082036d2c5a299
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ 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.0] - 2026-09-08
|
|
9
|
+
|
|
10
|
+
### BREAKING — the page snippet is now required
|
|
11
|
+
|
|
12
|
+
- **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.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **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.
|
|
17
|
+
|
|
18
|
+
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`).
|
|
19
|
+
|
|
20
|
+
## [0.9.0] - 2026-09-02
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **A dropped event or conversion now says so.** `Mbuzz.event` and `Mbuzz.conversion` return `false` when there is no `visitor_id` and no `user_id` — previously with no request, no log and no signal of any kind. That silence is what made the caching bug above invisible on a live account for a full day. They now warn at `Rails.logger.warn` (or stderr outside Rails), naming the dropped call and pointing at the session endpoint. Deliberately **not** gated behind `debug`: the people who hit this are precisely the ones not running in debug.
|
|
25
|
+
- **Attribution now survives a full-page cache.** A cached page is answered without entering the Rack stack, so the tracking middleware never ran, no visitor cookie was set, and every later event was dropped for having no one to attribute it to — silently, while the page rendered perfectly. `Mbuzz::Middleware::SessionEndpoint` answers `POST /_mbuzz/session`, a path caches don't store, and the server sets the cookie on that response. Mounted automatically in Rails; add it ahead of `Tracking` in Rack/Sinatra apps. See "Full-page caching" in the README for the one-time page snippet.
|
|
26
|
+
|
|
27
|
+
### Notes
|
|
28
|
+
|
|
29
|
+
- The visitor id is still never created or read in JavaScript. It stays `HttpOnly` and server-set, preserving its full two-year life — a cookie written by `document.cookie` is capped at 7 days under Safari's ITP, and 24 hours after an ad click.
|
|
30
|
+
- Inline the page snippet rather than serving it as a file: asset optimisers delay external scripts until first interaction, which would miss any visitor who lands and converts without clicking first.
|
|
31
|
+
|
|
8
32
|
## [0.8.2] - 2026-03-15
|
|
9
33
|
|
|
10
34
|
### Changed
|
data/README.md
CHANGED
|
@@ -188,10 +188,53 @@ require 'mbuzz'
|
|
|
188
188
|
|
|
189
189
|
Mbuzz.init(api_key: ENV['MBUZZ_API_KEY'])
|
|
190
190
|
|
|
191
|
+
use Mbuzz::Middleware::SessionEndpoint # see "Full-page caching" below
|
|
191
192
|
use Mbuzz::Middleware::Tracking
|
|
192
193
|
run MyApp
|
|
193
194
|
```
|
|
194
195
|
|
|
196
|
+
## Full-page caching — the snippet below is REQUIRED
|
|
197
|
+
|
|
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.
|
|
200
|
+
|
|
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.
|
|
212
|
+
|
|
213
|
+
Then call it once per page, from your layout:
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<script>
|
|
217
|
+
fetch('/_mbuzz/session', {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify({ url: location.href, referrer: document.referrer || '' }),
|
|
221
|
+
credentials: 'same-origin',
|
|
222
|
+
keepalive: true
|
|
223
|
+
}).catch(function () {});
|
|
224
|
+
</script>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Two things to keep as they are:
|
|
228
|
+
|
|
229
|
+
- **Inline the script, don't enqueue a file.** Asset optimisers (WP Rocket, LiteSpeed, and the
|
|
230
|
+
Rails equivalents) delay external scripts until the visitor first interacts. A visitor who
|
|
231
|
+
lands and converts without clicking anything first would never be established.
|
|
232
|
+
- **`credentials: 'same-origin'` is required**, or the cookie never comes back.
|
|
233
|
+
|
|
234
|
+
The visitor id is never created or read in JavaScript. It stays `HttpOnly` and server-set, which
|
|
235
|
+
is what preserves its full two-year life — a cookie written by `document.cookie` is capped at
|
|
236
|
+
7 days under Safari's ITP, and 24 hours after an ad click.
|
|
237
|
+
|
|
195
238
|
## Configuration Options
|
|
196
239
|
|
|
197
240
|
```ruby
|
|
@@ -19,7 +19,7 @@ module Mbuzz
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def call
|
|
22
|
-
return
|
|
22
|
+
return warn_dropped unless input_valid?
|
|
23
23
|
return proxy_result if proxy_accepted?
|
|
24
24
|
return false unless conversion_id
|
|
25
25
|
|
|
@@ -28,6 +28,11 @@ module Mbuzz
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
+
def warn_dropped
|
|
32
|
+
DroppedCall.warn_missing_identity("conversion", @conversion_type) unless has_identifier?
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
def input_valid?
|
|
32
37
|
has_identifier? && present?(@conversion_type) && hash?(@properties)
|
|
33
38
|
end
|
|
@@ -14,7 +14,7 @@ module Mbuzz
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def call
|
|
17
|
-
return
|
|
17
|
+
return warn_dropped unless input_valid?
|
|
18
18
|
return proxy_result if proxy_accepted?
|
|
19
19
|
return false unless event
|
|
20
20
|
|
|
@@ -24,6 +24,13 @@ module Mbuzz
|
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
+
# A drop with no request and no log is how this failure stayed invisible
|
|
28
|
+
# on a live account for a full day. Say it out loud.
|
|
29
|
+
def warn_dropped
|
|
30
|
+
DroppedCall.warn_missing_identity("event", @event_type) unless @user_id || @visitor_id
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
27
34
|
def input_valid?
|
|
28
35
|
present?(@event_type) && hash?(@properties) && (@user_id || @visitor_id)
|
|
29
36
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mbuzz
|
|
4
|
+
# Says out loud when a call is dropped for having nothing to attribute it to.
|
|
5
|
+
#
|
|
6
|
+
# Every SDK guarded its send with a bare `return false unless valid?`: no
|
|
7
|
+
# request, no log, nothing. Behind a full-page cache that is the whole failure
|
|
8
|
+
# — the cookie is never minted, so every later event fails this guard and
|
|
9
|
+
# vanishes. It cost a full day on a live account precisely because the silence
|
|
10
|
+
# was total from both sides.
|
|
11
|
+
#
|
|
12
|
+
# Deliberately NOT behind config.debug. The customers who hit this are exactly
|
|
13
|
+
# the ones not running in debug, so a debug-gated warning would be silent for
|
|
14
|
+
# everyone who needs it.
|
|
15
|
+
module DroppedCall
|
|
16
|
+
# Built lazily: SESSION_ENDPOINT_PATH is defined in mbuzz.rb *after* this
|
|
17
|
+
# file is required, so interpolating it at load time would not resolve.
|
|
18
|
+
def self.missing_identity
|
|
19
|
+
"no visitor_id and no user_id. If your pages are served from a full-page " \
|
|
20
|
+
"cache, mount Mbuzz::Middleware::SessionEndpoint and call POST " \
|
|
21
|
+
"#{SESSION_ENDPOINT_PATH} from the page — see the README's " \
|
|
22
|
+
"\"Full-page caching\" section."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.warn_missing_identity(kind, name)
|
|
26
|
+
emit("dropped #{kind} #{name.inspect}: #{missing_identity}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.warn_invalid(kind, name, reason)
|
|
30
|
+
emit("dropped #{kind} #{name.inspect}: #{reason}.")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.emit(message)
|
|
34
|
+
text = "[mbuzz] #{message}"
|
|
35
|
+
return Rails.logger.warn(text) if defined?(Rails) && Rails.logger
|
|
36
|
+
|
|
37
|
+
Kernel.warn(text)
|
|
38
|
+
end
|
|
39
|
+
private_class_method :emit
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack"
|
|
4
|
+
require "json"
|
|
5
|
+
require "digest"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
|
|
8
|
+
module Mbuzz
|
|
9
|
+
module Middleware
|
|
10
|
+
# Establishes the visitor from a page a cache served.
|
|
11
|
+
#
|
|
12
|
+
# A cached page never enters the Rack stack, so Tracking never runs and no
|
|
13
|
+
# visitor cookie is set — every later event is then rejected for having no
|
|
14
|
+
# one to attribute it to, silently, while the page renders perfectly.
|
|
15
|
+
#
|
|
16
|
+
# This endpoint is the one request on such a page that always reaches the
|
|
17
|
+
# app. A small script on the page POSTs here; the SERVER mints the cookie on
|
|
18
|
+
# the response. The id is never created or read in JS, so it stays HttpOnly
|
|
19
|
+
# and keeps its full two-year life — a cookie written by document.cookie is
|
|
20
|
+
# capped at 7 days under Safari's ITP, and 24 hours after an ad click.
|
|
21
|
+
#
|
|
22
|
+
# Mount ahead of Tracking:
|
|
23
|
+
#
|
|
24
|
+
# config.middleware.insert_before Mbuzz::Middleware::Tracking,
|
|
25
|
+
# Mbuzz::Middleware::SessionEndpoint
|
|
26
|
+
class SessionEndpoint
|
|
27
|
+
# Nothing to return: the response exists for its Set-Cookie header.
|
|
28
|
+
NO_CONTENT_STATUS = 204
|
|
29
|
+
|
|
30
|
+
def initialize(app)
|
|
31
|
+
@app = app
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def call(env)
|
|
35
|
+
return @app.call(env) unless session_request?(env)
|
|
36
|
+
|
|
37
|
+
mint(Rack::Request.new(env))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# POST only: a GET is cacheable by an intermediary, which would reintroduce
|
|
43
|
+
# the very bug this endpoint exists to fix.
|
|
44
|
+
def session_request?(env)
|
|
45
|
+
env["REQUEST_METHOD"] == "POST" &&
|
|
46
|
+
env["PATH_INFO"].to_s == SESSION_ENDPOINT_PATH
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mint(request)
|
|
50
|
+
context = build_context(request)
|
|
51
|
+
create_session_async(context)
|
|
52
|
+
|
|
53
|
+
[NO_CONTENT_STATUS, response_headers(context, request), []]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_context(request)
|
|
57
|
+
payload = parse_body(request)
|
|
58
|
+
ip = extract_ip(request)
|
|
59
|
+
user_agent = request.user_agent.to_s
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
visitor_id: resolve_visitor_id(request),
|
|
63
|
+
session_id: SecureRandom.uuid,
|
|
64
|
+
# The page's URL, not ours — a script on the page called us, so our own
|
|
65
|
+
# path would attribute every session to this endpoint.
|
|
66
|
+
url: payload["url"],
|
|
67
|
+
referrer: payload["referrer"],
|
|
68
|
+
ip: ip,
|
|
69
|
+
user_agent: user_agent,
|
|
70
|
+
device_fingerprint: Digest::SHA256.hexdigest("#{ip}|#{user_agent}")[0, 32]
|
|
71
|
+
}.freeze
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse_body(request)
|
|
75
|
+
body = request.body&.read.to_s
|
|
76
|
+
return {} if body.empty?
|
|
77
|
+
|
|
78
|
+
parsed = JSON.parse(body)
|
|
79
|
+
parsed.is_a?(Hash) ? parsed : {}
|
|
80
|
+
rescue JSON::ParserError
|
|
81
|
+
{}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_visitor_id(request)
|
|
85
|
+
request.cookies[VISITOR_COOKIE_NAME] || Visitor::Identifier.generate
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def create_session_async(context)
|
|
89
|
+
Thread.new do
|
|
90
|
+
Client.session(
|
|
91
|
+
visitor_id: context[:visitor_id],
|
|
92
|
+
session_id: context[:session_id],
|
|
93
|
+
url: context[:url],
|
|
94
|
+
referrer: context[:referrer],
|
|
95
|
+
device_fingerprint: context[:device_fingerprint],
|
|
96
|
+
user_agent: context[:user_agent]
|
|
97
|
+
)
|
|
98
|
+
rescue StandardError => e
|
|
99
|
+
log_error("Session creation failed: #{e.message}") if Mbuzz.config.debug
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def response_headers(context, request)
|
|
104
|
+
headers = { "cache-control" => "no-store, no-cache, must-revalidate, private" }
|
|
105
|
+
Rack::Utils.set_cookie_header!(headers, VISITOR_COOKIE_NAME, cookie_options(context, request))
|
|
106
|
+
headers
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def cookie_options(context, request)
|
|
110
|
+
options = {
|
|
111
|
+
value: context[:visitor_id],
|
|
112
|
+
max_age: VISITOR_COOKIE_MAX_AGE,
|
|
113
|
+
path: VISITOR_COOKIE_PATH,
|
|
114
|
+
httponly: true,
|
|
115
|
+
same_site: VISITOR_COOKIE_SAME_SITE
|
|
116
|
+
}
|
|
117
|
+
options[:secure] = true if request.ssl?
|
|
118
|
+
options
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def extract_ip(request)
|
|
122
|
+
forwarded = request.env["HTTP_X_FORWARDED_FOR"]
|
|
123
|
+
return forwarded.split(",").first.strip if forwarded
|
|
124
|
+
|
|
125
|
+
request.ip
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def log_error(message)
|
|
129
|
+
return unless defined?(Rails) && Rails.logger
|
|
130
|
+
|
|
131
|
+
Rails.logger.error("[Mbuzz] #{message}")
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -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
|
|
@@ -139,33 +143,6 @@ module Mbuzz
|
|
|
139
143
|
Rails.logger.error("[Mbuzz] #{message}")
|
|
140
144
|
end
|
|
141
145
|
|
|
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
146
|
# Store context in CurrentAttributes for background job propagation
|
|
170
147
|
def store_in_current_attributes(context, request)
|
|
171
148
|
return unless defined?(Mbuzz::Current)
|
data/lib/mbuzz/railtie.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
module Mbuzz
|
|
4
4
|
class Railtie < Rails::Railtie
|
|
5
5
|
initializer "mbuzz.configure_rails" do |app|
|
|
6
|
+
# The endpoint must sit ahead of Tracking: it answers its own path and
|
|
7
|
+
# never falls through, so Tracking should not also process it.
|
|
8
|
+
app.middleware.use Mbuzz::Middleware::SessionEndpoint
|
|
6
9
|
app.middleware.use Mbuzz::Middleware::Tracking
|
|
7
10
|
|
|
8
11
|
ActiveSupport.on_load(:action_controller) do
|
data/lib/mbuzz/version.rb
CHANGED
data/lib/mbuzz.rb
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "mbuzz/version"
|
|
4
4
|
require_relative "mbuzz/configuration"
|
|
5
|
+
require_relative "mbuzz/dropped_call"
|
|
5
6
|
require_relative "mbuzz/visitor/identifier"
|
|
6
7
|
require_relative "mbuzz/request_context"
|
|
7
8
|
require_relative "mbuzz/api"
|
|
8
9
|
require_relative "mbuzz/client"
|
|
9
10
|
require_relative "mbuzz/middleware/tracking"
|
|
11
|
+
require_relative "mbuzz/middleware/session_endpoint"
|
|
10
12
|
require_relative "mbuzz/controller_helpers"
|
|
11
13
|
|
|
12
14
|
# CurrentAttributes for automatic background job context propagation (Rails only)
|
|
@@ -27,6 +29,10 @@ module Mbuzz
|
|
|
27
29
|
VISITOR_COOKIE_PATH = "/"
|
|
28
30
|
VISITOR_COOKIE_SAME_SITE = "Lax"
|
|
29
31
|
|
|
32
|
+
# The one request a cached page always sends to the app. Kept off /api and
|
|
33
|
+
# /assets so host-app routing and cache rules don't shadow it.
|
|
34
|
+
SESSION_ENDPOINT_PATH = "/_mbuzz/session"
|
|
35
|
+
|
|
30
36
|
SESSION_USER_ID_KEY = "user_id"
|
|
31
37
|
ENV_USER_ID_KEY = "mbuzz.user_id"
|
|
32
38
|
ENV_VISITOR_ID_KEY = "mbuzz.visitor_id"
|
|
@@ -110,8 +116,13 @@ module Mbuzz
|
|
|
110
116
|
resolved_visitor_id = visitor_id || self.visitor_id
|
|
111
117
|
resolved_user_id = user_id
|
|
112
118
|
|
|
113
|
-
# Must have at least one identifier
|
|
114
|
-
|
|
119
|
+
# Must have at least one identifier. Warn rather than drop in silence: with
|
|
120
|
+
# no visitor and no user there is nothing to attribute this to, and behind a
|
|
121
|
+
# full-page cache that is the normal case, not an edge one.
|
|
122
|
+
unless resolved_visitor_id || resolved_user_id
|
|
123
|
+
DroppedCall.warn_missing_identity("event", event_type)
|
|
124
|
+
return false
|
|
125
|
+
end
|
|
115
126
|
|
|
116
127
|
Client.track(
|
|
117
128
|
visitor_id: resolved_visitor_id,
|
|
@@ -161,8 +172,12 @@ module Mbuzz
|
|
|
161
172
|
resolved_visitor_id = visitor_id || self.visitor_id
|
|
162
173
|
resolved_user_id = user_id || self.user_id
|
|
163
174
|
|
|
164
|
-
# Must have at least one identifier (visitor_id or user_id)
|
|
165
|
-
|
|
175
|
+
# Must have at least one identifier (visitor_id or user_id). A conversion
|
|
176
|
+
# dropped here is lost revenue attribution, so it is never silent.
|
|
177
|
+
unless resolved_visitor_id || resolved_user_id
|
|
178
|
+
DroppedCall.warn_missing_identity("conversion", conversion_type)
|
|
179
|
+
return false
|
|
180
|
+
end
|
|
166
181
|
|
|
167
182
|
Client.conversion(
|
|
168
183
|
visitor_id: resolved_visitor_id,
|
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.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- mbuzz team
|
|
@@ -32,13 +32,10 @@ executables: []
|
|
|
32
32
|
extensions: []
|
|
33
33
|
extra_rdoc_files: []
|
|
34
34
|
files:
|
|
35
|
-
- ".DS_Store"
|
|
36
35
|
- CHANGELOG.md
|
|
37
|
-
- CHECK_BUG.md
|
|
38
36
|
- LICENSE.txt
|
|
39
37
|
- README.md
|
|
40
38
|
- Rakefile
|
|
41
|
-
- lib/.DS_Store
|
|
42
39
|
- lib/mbuzz.rb
|
|
43
40
|
- lib/mbuzz/api.rb
|
|
44
41
|
- lib/mbuzz/client.rb
|
|
@@ -49,18 +46,13 @@ files:
|
|
|
49
46
|
- lib/mbuzz/configuration.rb
|
|
50
47
|
- lib/mbuzz/controller_helpers.rb
|
|
51
48
|
- lib/mbuzz/current.rb
|
|
49
|
+
- lib/mbuzz/dropped_call.rb
|
|
50
|
+
- lib/mbuzz/middleware/session_endpoint.rb
|
|
52
51
|
- lib/mbuzz/middleware/tracking.rb
|
|
53
52
|
- lib/mbuzz/railtie.rb
|
|
54
53
|
- lib/mbuzz/request_context.rb
|
|
55
54
|
- lib/mbuzz/version.rb
|
|
56
55
|
- lib/mbuzz/visitor/identifier.rb
|
|
57
|
-
- lib/specs/old/SPECIFICATION.md
|
|
58
|
-
- lib/specs/old/conversions.md
|
|
59
|
-
- lib/specs/old/event_ids_response.md
|
|
60
|
-
- lib/specs/old/v0.2.0_breaking_changes.md
|
|
61
|
-
- lib/specs/old/v2.0.0_sessions_upgrade.md
|
|
62
|
-
- lib/specs/v0.5.0_four_call_model.md
|
|
63
|
-
- lib/specs/v0.7.0_deterministic_sessions.md
|
|
64
56
|
- sig/mbuzz.rbs
|
|
65
57
|
homepage: https://mbuzz.co
|
|
66
58
|
licenses:
|
data/.DS_Store
DELETED
|
Binary file
|