mbuzz 0.7.0__py3-none-any.whl → 0.7.3__py3-none-any.whl

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.
mbuzz/__init__.py CHANGED
@@ -9,7 +9,7 @@ from .client.track import track, TrackResult
9
9
  from .client.identify import identify
10
10
  from .client.conversion import conversion, ConversionResult
11
11
 
12
- __version__ = "0.7.0"
12
+ __version__ = "0.7.3"
13
13
 
14
14
 
15
15
  def init(
mbuzz/api.py CHANGED
@@ -10,7 +10,7 @@ from .config import config
10
10
 
11
11
  logger = logging.getLogger("mbuzz")
12
12
 
13
- VERSION = "0.1.0"
13
+ VERSION = "0.7.3"
14
14
 
15
15
 
16
16
  def post(path: str, payload: Dict[str, Any]) -> bool:
mbuzz/middleware/flask.py CHANGED
@@ -1,14 +1,46 @@
1
1
  """Flask middleware for mbuzz tracking."""
2
2
  # NOTE: Session cookie removed in 0.7.0 - server handles session resolution
3
3
 
4
+ import threading
5
+ import uuid
6
+ from datetime import datetime, timezone
7
+ from typing import Optional
8
+
4
9
  from flask import Flask, request, g, Response
5
10
 
11
+ from ..api import post
6
12
  from ..config import config
7
13
  from ..context import RequestContext, set_context, clear_context
8
14
  from ..cookies import VISITOR_COOKIE, VISITOR_MAX_AGE
15
+ from ..utils.fingerprint import device_fingerprint
9
16
  from ..utils.identifier import generate_id
10
17
 
11
18
 
19
+ def should_create_session() -> bool:
20
+ """Determine whether this request is a real page navigation.
21
+
22
+ Primary signal: Sec-Fetch-* headers (modern browsers, unforgeable).
23
+ Fallback: blacklist known sub-request framework headers (old browsers/bots).
24
+ """
25
+ mode = request.headers.get("Sec-Fetch-Mode")
26
+ dest = request.headers.get("Sec-Fetch-Dest")
27
+
28
+ if mode:
29
+ return (
30
+ mode == "navigate"
31
+ and dest == "document"
32
+ and not request.headers.get("Sec-Purpose")
33
+ )
34
+
35
+ # Fallback for old browsers / bots: blacklist known sub-requests
36
+ return (
37
+ not request.headers.get("Turbo-Frame")
38
+ and not request.headers.get("HX-Request")
39
+ and not request.headers.get("X-Up-Version")
40
+ and request.headers.get("X-Requested-With") != "XMLHttpRequest"
41
+ )
42
+
43
+
12
44
  def init_app(app: Flask) -> None:
13
45
  """Initialize mbuzz tracking for Flask app."""
14
46
 
@@ -24,6 +56,11 @@ def init_app(app: Flask) -> None:
24
56
  _set_request_context(visitor_id, ip, user_agent)
25
57
  _store_in_g(visitor_id)
26
58
 
59
+ if should_create_session():
60
+ _create_session_async(
61
+ visitor_id, request.url, request.referrer, ip, user_agent
62
+ )
63
+
27
64
  @app.after_request
28
65
  def after_request(response: Response) -> Response:
29
66
  if not hasattr(g, "mbuzz_visitor_id"):
@@ -83,6 +120,34 @@ def _store_in_g(visitor_id: str) -> None:
83
120
  g.mbuzz_is_new_visitor = VISITOR_COOKIE not in request.cookies
84
121
 
85
122
 
123
+ def _create_session_async(
124
+ visitor_id: str,
125
+ url: str,
126
+ referrer: Optional[str],
127
+ ip: str,
128
+ user_agent: str,
129
+ ) -> None:
130
+ """Fire-and-forget session creation via background thread.
131
+
132
+ All data is captured before the thread starts — no request-object
133
+ access inside the thread (it would be invalid after the response).
134
+ """
135
+ payload = {
136
+ "session": {
137
+ "visitor_id": visitor_id,
138
+ "session_id": str(uuid.uuid4()),
139
+ "url": url,
140
+ "referrer": referrer,
141
+ "device_fingerprint": device_fingerprint(ip, user_agent),
142
+ "started_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
143
+ }
144
+ }
145
+
146
+ threading.Thread(
147
+ target=post, args=("/sessions", payload), daemon=True
148
+ ).start()
149
+
150
+
86
151
  def _set_cookies(response: Response) -> None:
87
152
  """Set visitor cookie on response."""
88
153
  secure = request.is_secure
mbuzz/utils/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Utility functions for mbuzz SDK."""
2
2
 
3
3
  from .identifier import generate_id
4
+ from .fingerprint import device_fingerprint
4
5
 
5
- __all__ = ["generate_id"]
6
+ __all__ = ["generate_id", "device_fingerprint"]
@@ -0,0 +1,12 @@
1
+ """Device fingerprint generation — matches server-side SHA256(ip|user_agent)[0:32]."""
2
+
3
+ import hashlib
4
+
5
+
6
+ def device_fingerprint(ip: str, user_agent: str) -> str:
7
+ """Compute a device fingerprint from IP and User-Agent.
8
+
9
+ Produces a 32-char hex string identical to the server-side computation
10
+ and the Ruby/Node SDKs.
11
+ """
12
+ return hashlib.sha256(f"{ip}|{user_agent}".encode()).hexdigest()[:32]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbuzz
3
- Version: 0.7.0
3
+ Version: 0.7.3
4
4
  Summary: Multi-touch attribution SDK for Python
5
5
  Project-URL: Homepage, https://mbuzz.co
6
6
  Project-URL: Documentation, https://mbuzz.co/docs/getting-started
@@ -1,5 +1,5 @@
1
- mbuzz/__init__.py,sha256=tIBoql6_TTtWXErayliL98YGa-EIFMq9rE-X2vgfVHk,2150
2
- mbuzz/api.py,sha256=BITkW6TQEGmPA0OmYFFC611X6JWiY6C1dY-_wsKDT2U,2107
1
+ mbuzz/__init__.py,sha256=OQDjNSQTB6Yfc66NRpZPN8cWjWcVJopDysoB8tb3cbU,2150
2
+ mbuzz/api.py,sha256=bOLjQRgv9PbfGvNxYMtdyiBQvlLFHsHcVgZ8_lEWEHU,2107
3
3
  mbuzz/config.py,sha256=u6v1WnMTgiqyDXHszlXw5hyNENgYRy7-z-IWKuYp0-o,2399
4
4
  mbuzz/context.py,sha256=tcvtKaAbHzH8dHGkKviNgXF31IKgintqo0lhb6uU4dU,1308
5
5
  mbuzz/cookies.py,sha256=pDcvSq0ulzKrFLf5UK408s9Lv2pVzLANVaF_-P7iG3c,194
@@ -8,9 +8,10 @@ mbuzz/client/conversion.py,sha256=mUkxU8FgbmvvXnliTskawnDzbwn1SxutZ5E4rFkNt5o,31
8
8
  mbuzz/client/identify.py,sha256=duyd-OWdBo4w7-7Nv5X-GeG2IpMI-1G_kYwnDXup0FE,859
9
9
  mbuzz/client/track.py,sha256=NReHqXWx24cPhIlc7UBQjj9QVdnXfrJsFzS5UmOUMtA,4354
10
10
  mbuzz/middleware/__init__.py,sha256=gIjwTArToaQNB2NC0iPE_RcmzuHjH7-7jjd7_Dyq4Pw,37
11
- mbuzz/middleware/flask.py,sha256=ccotvyzS4PB5-cfaplpwGJJOQzR90hy5iz6sTrhwXXc,2687
12
- mbuzz/utils/__init__.py,sha256=-ejtRN6CTkV3D8uujDtAPDoMT0DAG-ULpWOF-Ae55dY,103
11
+ mbuzz/middleware/flask.py,sha256=gkvwtgfLasTKq64lUvK_9I2PeOR9oBRbrWpQZbqnaAk,4656
12
+ mbuzz/utils/__init__.py,sha256=tq5wgr4Cy-PFl2tvpN5VYHzPd3M-e5qcxnv-McoTopI,169
13
+ mbuzz/utils/fingerprint.py,sha256=T2plrP9n70LkoCvGbBCh7nxZRfdQpCd_CdZBseBfzCg,410
13
14
  mbuzz/utils/identifier.py,sha256=iAYmd4he2RTtTNlmszb6KQmD9McZbSMgZZdkMuoGOCE,174
14
- mbuzz-0.7.0.dist-info/METADATA,sha256=phnoKfsZaw8nA4u3UTE9uF6n9TkXxzGSAOBGKi35J_U,1849
15
- mbuzz-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- mbuzz-0.7.0.dist-info/RECORD,,
15
+ mbuzz-0.7.3.dist-info/METADATA,sha256=Q0dHPns1Lk73_ziPwsknxzQMIkJFAYnqRo5drKkCD_0,1849
16
+ mbuzz-0.7.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ mbuzz-0.7.3.dist-info/RECORD,,
File without changes