mbuzz 0.2.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
@@ -1,4 +1,5 @@
1
1
  """Mbuzz - Multi-touch attribution SDK for Python."""
2
+ # NOTE: Session ID removed in 0.7.0 - server handles session resolution
2
3
 
3
4
  from typing import Any, Dict, Optional, Union
4
5
 
@@ -8,7 +9,7 @@ from .client.track import track, TrackResult
8
9
  from .client.identify import identify
9
10
  from .client.conversion import conversion, ConversionResult
10
11
 
11
- __version__ = "0.2.0"
12
+ __version__ = "0.7.3"
12
13
 
13
14
 
14
15
  def init(
@@ -61,12 +62,6 @@ def visitor_id() -> Optional[str]:
61
62
  return ctx.visitor_id if ctx else None
62
63
 
63
64
 
64
- def session_id() -> Optional[str]:
65
- """Get current session ID from context."""
66
- ctx = get_context()
67
- return ctx.session_id if ctx else None
68
-
69
-
70
65
  def user_id() -> Optional[str]:
71
66
  """Get current user ID from context."""
72
67
  ctx = get_context()
@@ -79,7 +74,6 @@ __all__ = [
79
74
  "conversion",
80
75
  "identify",
81
76
  "visitor_id",
82
- "session_id",
83
77
  "user_id",
84
78
  "TrackResult",
85
79
  "ConversionResult",
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:
@@ -1,4 +1,5 @@
1
1
  """Conversion request for tracking conversions."""
2
+ # NOTE: Session ID removed in 0.7.0 - server handles session resolution
2
3
 
3
4
  from dataclasses import dataclass
4
5
  from typing import Any, Dict, Optional, Union
@@ -26,6 +27,9 @@ def conversion(
26
27
  is_acquisition: bool = False,
27
28
  inherit_acquisition: bool = False,
28
29
  properties: Optional[Dict[str, Any]] = None,
30
+ ip: Optional[str] = None,
31
+ user_agent: Optional[str] = None,
32
+ identifier: Optional[Dict[str, str]] = None,
29
33
  ) -> ConversionResult:
30
34
  """Track a conversion.
31
35
 
@@ -39,6 +43,9 @@ def conversion(
39
43
  is_acquisition: Whether this is a customer acquisition
40
44
  inherit_acquisition: Whether to inherit acquisition from previous conversion
41
45
  properties: Additional conversion properties
46
+ ip: Client IP address for server-side session resolution
47
+ user_agent: Client user agent for server-side session resolution
48
+ identifier: Cross-device identifier (email, user_id, etc.)
42
49
 
43
50
  Returns:
44
51
  ConversionResult with success status, conversion ID, and attribution data
@@ -47,22 +54,36 @@ def conversion(
47
54
 
48
55
  visitor_id = visitor_id or (ctx.visitor_id if ctx else None)
49
56
  user_id = user_id or (ctx.user_id if ctx else None)
57
+ ip = ip or (ctx.ip if ctx else None)
58
+ user_agent = user_agent or (ctx.user_agent if ctx else None)
50
59
 
51
60
  if not visitor_id and not user_id:
52
61
  return ConversionResult(success=False)
53
62
 
54
- payload = {
63
+ payload: Dict[str, Any] = {
55
64
  "conversion_type": conversion_type,
56
- "visitor_id": visitor_id,
57
- "user_id": str(user_id) if user_id else None,
58
- "event_id": event_id,
59
- "revenue": revenue,
60
65
  "currency": currency,
61
66
  "is_acquisition": is_acquisition,
62
67
  "inherit_acquisition": inherit_acquisition,
63
68
  "properties": properties or {},
64
69
  }
65
70
 
71
+ # Only include non-None values
72
+ if visitor_id:
73
+ payload["visitor_id"] = visitor_id
74
+ if user_id:
75
+ payload["user_id"] = str(user_id)
76
+ if event_id:
77
+ payload["event_id"] = event_id
78
+ if revenue is not None:
79
+ payload["revenue"] = revenue
80
+ if ip:
81
+ payload["ip"] = ip
82
+ if user_agent:
83
+ payload["user_agent"] = user_agent
84
+ if identifier:
85
+ payload["identifier"] = identifier
86
+
66
87
  response = post_with_response("/conversions", payload)
67
88
  if not response:
68
89
  return ConversionResult(success=False)
mbuzz/client/track.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Track request for event tracking."""
2
+ # NOTE: Session ID removed in 0.7.0 - server handles session resolution
2
3
 
3
4
  from dataclasses import dataclass
4
5
  from datetime import datetime, timezone
@@ -16,7 +17,6 @@ class TrackResult:
16
17
  event_id: Optional[str] = None
17
18
  event_type: Optional[str] = None
18
19
  visitor_id: Optional[str] = None
19
- session_id: Optional[str] = None
20
20
 
21
21
 
22
22
  @dataclass
@@ -25,15 +25,15 @@ class TrackOptions:
25
25
 
26
26
  event_type: str
27
27
  visitor_id: Optional[str] = None
28
- session_id: Optional[str] = None
29
28
  user_id: Optional[str] = None
30
29
  properties: Optional[Dict[str, Any]] = None
31
30
  ip: Optional[str] = None
32
31
  user_agent: Optional[str] = None
32
+ identifier: Optional[Dict[str, str]] = None
33
33
 
34
34
 
35
35
  def _resolve_ids(options: TrackOptions) -> TrackOptions:
36
- """Resolve visitor/session/user IDs and ip/user_agent from context if not provided."""
36
+ """Resolve visitor/user IDs and ip/user_agent from context if not provided."""
37
37
  ctx = get_context()
38
38
  if not ctx:
39
39
  return options
@@ -41,11 +41,11 @@ def _resolve_ids(options: TrackOptions) -> TrackOptions:
41
41
  return TrackOptions(
42
42
  event_type=options.event_type,
43
43
  visitor_id=options.visitor_id or ctx.visitor_id,
44
- session_id=options.session_id or ctx.session_id,
45
44
  user_id=options.user_id or ctx.user_id,
46
45
  properties=options.properties,
47
46
  ip=options.ip or ctx.ip,
48
47
  user_agent=options.user_agent or ctx.user_agent,
48
+ identifier=options.identifier,
49
49
  )
50
50
 
51
51
 
@@ -66,19 +66,23 @@ def _validate(options: TrackOptions) -> bool:
66
66
 
67
67
  def _build_payload(options: TrackOptions, properties: Dict[str, Any]) -> Dict[str, Any]:
68
68
  """Build API payload from options."""
69
- event = {
69
+ event: Dict[str, Any] = {
70
70
  "event_type": options.event_type,
71
- "visitor_id": options.visitor_id,
72
- "session_id": options.session_id,
73
- "user_id": options.user_id,
74
71
  "properties": properties,
75
72
  "timestamp": datetime.now(timezone.utc).isoformat(),
76
73
  }
77
- # Add ip and user_agent only if provided (for server-side session resolution)
74
+
75
+ # Only include non-None values
76
+ if options.visitor_id:
77
+ event["visitor_id"] = options.visitor_id
78
+ if options.user_id:
79
+ event["user_id"] = options.user_id
78
80
  if options.ip:
79
81
  event["ip"] = options.ip
80
82
  if options.user_agent:
81
83
  event["user_agent"] = options.user_agent
84
+ if options.identifier:
85
+ event["identifier"] = options.identifier
82
86
 
83
87
  return {"events": [event]}
84
88
 
@@ -94,29 +98,28 @@ def _parse_response(response: Optional[Dict[str, Any]], options: TrackOptions) -
94
98
  event_id=event.get("id"),
95
99
  event_type=options.event_type,
96
100
  visitor_id=options.visitor_id,
97
- session_id=options.session_id,
98
101
  )
99
102
 
100
103
 
101
104
  def track(
102
105
  event_type: str,
103
106
  visitor_id: Optional[str] = None,
104
- session_id: Optional[str] = None,
105
107
  user_id: Optional[str] = None,
106
108
  properties: Optional[Dict[str, Any]] = None,
107
109
  ip: Optional[str] = None,
108
110
  user_agent: Optional[str] = None,
111
+ identifier: Optional[Dict[str, str]] = None,
109
112
  ) -> TrackResult:
110
113
  """Track an event.
111
114
 
112
115
  Args:
113
116
  event_type: Type of event (e.g., "page_view", "button_click")
114
117
  visitor_id: Visitor ID (uses context if not provided)
115
- session_id: Session ID (uses context if not provided)
116
118
  user_id: User ID (uses context if not provided)
117
119
  properties: Additional event properties
118
120
  ip: Client IP address for server-side session resolution
119
121
  user_agent: Client user agent for server-side session resolution
122
+ identifier: Cross-device identifier (email, user_id, etc.)
120
123
 
121
124
  Returns:
122
125
  TrackResult with success status and event details
@@ -124,11 +127,11 @@ def track(
124
127
  options = TrackOptions(
125
128
  event_type=event_type,
126
129
  visitor_id=visitor_id,
127
- session_id=session_id,
128
130
  user_id=user_id,
129
131
  properties=properties,
130
132
  ip=ip,
131
133
  user_agent=user_agent,
134
+ identifier=identifier,
132
135
  )
133
136
 
134
137
  options = _resolve_ids(options)
mbuzz/context.py CHANGED
@@ -1,4 +1,5 @@
1
1
  """Request context management using contextvars."""
2
+ # NOTE: Session ID removed in 0.7.0 - server handles session resolution
2
3
 
3
4
  from contextvars import ContextVar
4
5
  from dataclasses import dataclass
@@ -10,12 +11,11 @@ class RequestContext:
10
11
  """Holds request-scoped data for tracking."""
11
12
 
12
13
  visitor_id: str
13
- session_id: str
14
+ ip: str
15
+ user_agent: str
14
16
  user_id: Optional[str] = None
15
17
  url: Optional[str] = None
16
18
  referrer: Optional[str] = None
17
- ip: Optional[str] = None
18
- user_agent: Optional[str] = None
19
19
 
20
20
  def enrich_properties(self, properties: Dict[str, Any]) -> Dict[str, Any]:
21
21
  """Add url and referrer to properties if not already present."""
mbuzz/cookies.py CHANGED
@@ -1,7 +1,5 @@
1
1
  """Cookie constants for mbuzz SDK."""
2
+ # NOTE: Session cookie removed in 0.7.0 - server handles session resolution
2
3
 
3
4
  VISITOR_COOKIE = "_mbuzz_vid"
4
- SESSION_COOKIE = "_mbuzz_sid"
5
-
6
5
  VISITOR_MAX_AGE = 63072000 # 2 years in seconds
7
- SESSION_MAX_AGE = 1800 # 30 minutes in seconds
mbuzz/middleware/flask.py CHANGED
@@ -1,15 +1,44 @@
1
1
  """Flask middleware for mbuzz tracking."""
2
+ # NOTE: Session cookie removed in 0.7.0 - server handles session resolution
2
3
 
3
4
  import threading
4
- from flask import Flask, request, g, Response
5
+ import uuid
6
+ from datetime import datetime, timezone
5
7
  from typing import Optional
6
8
 
9
+ from flask import Flask, request, g, Response
10
+
11
+ from ..api import post
7
12
  from ..config import config
8
13
  from ..context import RequestContext, set_context, clear_context
9
- from ..cookies import VISITOR_COOKIE, SESSION_COOKIE, VISITOR_MAX_AGE, SESSION_MAX_AGE
14
+ from ..cookies import VISITOR_COOKIE, VISITOR_MAX_AGE
15
+ from ..utils.fingerprint import device_fingerprint
10
16
  from ..utils.identifier import generate_id
11
- from ..utils.session_id import generate_deterministic, generate_from_fingerprint
12
- from ..client.session import create_session
17
+
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
+ )
13
42
 
14
43
 
15
44
  def init_app(app: Flask) -> None:
@@ -21,12 +50,16 @@ def init_app(app: Flask) -> None:
21
50
  return
22
51
 
23
52
  visitor_id = _get_or_create_visitor_id()
24
- session_id = _get_or_create_session_id()
25
- is_new_session = SESSION_COOKIE not in request.cookies
53
+ ip = _get_client_ip()
54
+ user_agent = _get_user_agent()
55
+
56
+ _set_request_context(visitor_id, ip, user_agent)
57
+ _store_in_g(visitor_id)
26
58
 
27
- _set_request_context(visitor_id, session_id)
28
- _store_in_g(visitor_id, session_id, is_new_session)
29
- _create_session_if_new(visitor_id, session_id, is_new_session)
59
+ if should_create_session():
60
+ _create_session_async(
61
+ visitor_id, request.url, request.referrer, ip, user_agent
62
+ )
30
63
 
31
64
  @app.after_request
32
65
  def after_request(response: Response) -> Response:
@@ -55,19 +88,6 @@ def _get_or_create_visitor_id() -> str:
55
88
  return request.cookies.get(VISITOR_COOKIE) or generate_id()
56
89
 
57
90
 
58
- def _get_or_create_session_id() -> str:
59
- """Get session ID from cookie or generate deterministic one."""
60
- existing = request.cookies.get(SESSION_COOKIE)
61
- if existing:
62
- return existing
63
-
64
- existing_visitor_id = request.cookies.get(VISITOR_COOKIE)
65
- if existing_visitor_id:
66
- return generate_deterministic(existing_visitor_id)
67
- else:
68
- return generate_from_fingerprint(_get_client_ip(), _get_user_agent())
69
-
70
-
71
91
  def _get_client_ip() -> str:
72
92
  """Get client IP from request headers."""
73
93
  forwarded = request.headers.get("X-Forwarded-For", "")
@@ -81,11 +101,12 @@ def _get_user_agent() -> str:
81
101
  return request.headers.get("User-Agent", "unknown")
82
102
 
83
103
 
84
- def _set_request_context(visitor_id: str, session_id: str) -> None:
104
+ def _set_request_context(visitor_id: str, ip: str, user_agent: str) -> None:
85
105
  """Set request context for tracking calls."""
86
106
  ctx = RequestContext(
87
107
  visitor_id=visitor_id,
88
- session_id=session_id,
108
+ ip=ip,
109
+ user_agent=user_agent,
89
110
  user_id=None,
90
111
  url=request.url,
91
112
  referrer=request.referrer,
@@ -93,34 +114,42 @@ def _set_request_context(visitor_id: str, session_id: str) -> None:
93
114
  set_context(ctx)
94
115
 
95
116
 
96
- def _store_in_g(visitor_id: str, session_id: str, is_new_session: bool) -> None:
117
+ def _store_in_g(visitor_id: str) -> None:
97
118
  """Store tracking IDs in Flask g object for after_request."""
98
119
  g.mbuzz_visitor_id = visitor_id
99
- g.mbuzz_session_id = session_id
100
120
  g.mbuzz_is_new_visitor = VISITOR_COOKIE not in request.cookies
101
- g.mbuzz_is_new_session = is_new_session
102
121
 
103
122
 
104
- def _create_session_if_new(visitor_id: str, session_id: str, is_new_session: bool) -> None:
105
- """Create session asynchronously if new session."""
106
- if not is_new_session:
107
- return
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
+ }
108
145
 
109
- ctx = RequestContext(
110
- visitor_id=visitor_id,
111
- session_id=session_id,
112
- url=request.url,
113
- referrer=request.referrer,
114
- )
115
146
  threading.Thread(
116
- target=create_session,
117
- args=(visitor_id, session_id, ctx.url, ctx.referrer),
118
- daemon=True
147
+ target=post, args=("/sessions", payload), daemon=True
119
148
  ).start()
120
149
 
121
150
 
122
151
  def _set_cookies(response: Response) -> None:
123
- """Set visitor and session cookies on response."""
152
+ """Set visitor cookie on response."""
124
153
  secure = request.is_secure
125
154
 
126
155
  response.set_cookie(
@@ -131,11 +160,3 @@ def _set_cookies(response: Response) -> None:
131
160
  samesite="Lax",
132
161
  secure=secure,
133
162
  )
134
- response.set_cookie(
135
- SESSION_COOKIE,
136
- g.mbuzz_session_id,
137
- max_age=SESSION_MAX_AGE,
138
- httponly=True,
139
- samesite="Lax",
140
- secure=secure,
141
- )
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.2.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
@@ -0,0 +1,17 @@
1
+ mbuzz/__init__.py,sha256=OQDjNSQTB6Yfc66NRpZPN8cWjWcVJopDysoB8tb3cbU,2150
2
+ mbuzz/api.py,sha256=bOLjQRgv9PbfGvNxYMtdyiBQvlLFHsHcVgZ8_lEWEHU,2107
3
+ mbuzz/config.py,sha256=u6v1WnMTgiqyDXHszlXw5hyNENgYRy7-z-IWKuYp0-o,2399
4
+ mbuzz/context.py,sha256=tcvtKaAbHzH8dHGkKviNgXF31IKgintqo0lhb6uU4dU,1308
5
+ mbuzz/cookies.py,sha256=pDcvSq0ulzKrFLf5UK408s9Lv2pVzLANVaF_-P7iG3c,194
6
+ mbuzz/client/__init__.py,sha256=oNOTpPKdt-b_VH_f848OQTbEEDFhlpbEjZ-WjKvh3JI,36
7
+ mbuzz/client/conversion.py,sha256=mUkxU8FgbmvvXnliTskawnDzbwn1SxutZ5E4rFkNt5o,3155
8
+ mbuzz/client/identify.py,sha256=duyd-OWdBo4w7-7Nv5X-GeG2IpMI-1G_kYwnDXup0FE,859
9
+ mbuzz/client/track.py,sha256=NReHqXWx24cPhIlc7UBQjj9QVdnXfrJsFzS5UmOUMtA,4354
10
+ mbuzz/middleware/__init__.py,sha256=gIjwTArToaQNB2NC0iPE_RcmzuHjH7-7jjd7_Dyq4Pw,37
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
14
+ mbuzz/utils/identifier.py,sha256=iAYmd4he2RTtTNlmszb6KQmD9McZbSMgZZdkMuoGOCE,174
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,,
mbuzz/client/session.py DELETED
@@ -1,38 +0,0 @@
1
- """Session request for creating sessions."""
2
-
3
- from datetime import datetime, timezone
4
- from typing import Optional
5
-
6
- from ..api import post
7
-
8
-
9
- def create_session(
10
- visitor_id: str,
11
- session_id: str,
12
- url: str,
13
- referrer: Optional[str] = None,
14
- ) -> bool:
15
- """Create a new session.
16
-
17
- Called async from middleware on first request.
18
-
19
- Args:
20
- visitor_id: Visitor ID
21
- session_id: Session ID
22
- url: Current page URL
23
- referrer: Referring URL
24
-
25
- Returns:
26
- True on success, False on failure
27
- """
28
- payload = {
29
- "session": {
30
- "visitor_id": visitor_id,
31
- "session_id": session_id,
32
- "url": url,
33
- "referrer": referrer,
34
- "started_at": datetime.now(timezone.utc).isoformat(),
35
- }
36
- }
37
-
38
- return post("/sessions", payload)
mbuzz/utils/session_id.py DELETED
@@ -1,39 +0,0 @@
1
- """Deterministic session ID generation."""
2
-
3
- import hashlib
4
- import secrets
5
- import time
6
-
7
- SESSION_TIMEOUT_SECONDS = 1800
8
- SESSION_ID_LENGTH = 64
9
- FINGERPRINT_LENGTH = 32
10
-
11
-
12
- def generate_deterministic(visitor_id: str, timestamp: int | None = None) -> str:
13
- """Generate session ID for returning visitors."""
14
- if timestamp is None:
15
- timestamp = int(time.time())
16
- time_bucket = timestamp // SESSION_TIMEOUT_SECONDS
17
- raw = f"{visitor_id}_{time_bucket}"
18
- return hashlib.sha256(raw.encode()).hexdigest()[:SESSION_ID_LENGTH]
19
-
20
-
21
- def generate_from_fingerprint(
22
- client_ip: str,
23
- user_agent: str,
24
- timestamp: int | None = None
25
- ) -> str:
26
- """Generate session ID for new visitors using IP+UA fingerprint."""
27
- if timestamp is None:
28
- timestamp = int(time.time())
29
- fingerprint = hashlib.sha256(
30
- f"{client_ip}|{user_agent}".encode()
31
- ).hexdigest()[:FINGERPRINT_LENGTH]
32
- time_bucket = timestamp // SESSION_TIMEOUT_SECONDS
33
- raw = f"{fingerprint}_{time_bucket}"
34
- return hashlib.sha256(raw.encode()).hexdigest()[:SESSION_ID_LENGTH]
35
-
36
-
37
- def generate_random() -> str:
38
- """Generate random session ID (fallback)."""
39
- return secrets.token_hex(32)
@@ -1,18 +0,0 @@
1
- mbuzz/__init__.py,sha256=ip9lSCsNe7T5k1qOfGs4pttoTNGx5Ws-Zf9fHoWXFPg,2247
2
- mbuzz/api.py,sha256=BITkW6TQEGmPA0OmYFFC611X6JWiY6C1dY-_wsKDT2U,2107
3
- mbuzz/config.py,sha256=u6v1WnMTgiqyDXHszlXw5hyNENgYRy7-z-IWKuYp0-o,2399
4
- mbuzz/context.py,sha256=mM9Hi8GnqTkAVCqB_VKGCXl7a5AFME4WTM-2DCIji_w,1290
5
- mbuzz/cookies.py,sha256=MCpMljQXaqJ2bhEtTv1aKR2fuqLfGBFofjPRayAQGpw,197
6
- mbuzz/client/__init__.py,sha256=oNOTpPKdt-b_VH_f848OQTbEEDFhlpbEjZ-WjKvh3JI,36
7
- mbuzz/client/conversion.py,sha256=Ua3ngFWmH6ekm3ZanNf5up_jV8t5nf788ipM1usvZoM,2346
8
- mbuzz/client/identify.py,sha256=duyd-OWdBo4w7-7Nv5X-GeG2IpMI-1G_kYwnDXup0FE,859
9
- mbuzz/client/session.py,sha256=mk8v892bvpeW-VNKFQYDsW442w881yfXBiM4sr9a0SU,835
10
- mbuzz/client/track.py,sha256=HWRYWDWJ_WW_k5IHtkAn4s_MmVwSVFxsxR76KG2h_Ps,4288
11
- mbuzz/middleware/__init__.py,sha256=gIjwTArToaQNB2NC0iPE_RcmzuHjH7-7jjd7_Dyq4Pw,37
12
- mbuzz/middleware/flask.py,sha256=s3U77A2986L-KvWUH9MmqSwJwQWzQTYlZPZXH-wzE9o,4182
13
- mbuzz/utils/__init__.py,sha256=-ejtRN6CTkV3D8uujDtAPDoMT0DAG-ULpWOF-Ae55dY,103
14
- mbuzz/utils/identifier.py,sha256=iAYmd4he2RTtTNlmszb6KQmD9McZbSMgZZdkMuoGOCE,174
15
- mbuzz/utils/session_id.py,sha256=3XJkzPg5iUIbRYzz5LThfS8WTAmlmtn60WZTA_TlVhk,1185
16
- mbuzz-0.2.0.dist-info/METADATA,sha256=3Z8snpq6fv0PmZUvSuAjsH1KaiZIpE9pRLVHTaw8cPE,1849
17
- mbuzz-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
- mbuzz-0.2.0.dist-info/RECORD,,
File without changes