Skip to content

Commit e3846c0

Browse files
committed
feat: deterministic daily session ID using sha256(machine_id + app_key + date)
Replace the 4-hour sliding inactivity timeout with a deterministic session ID derived from the machine ID, app key, and UTC date. Same device + same app + same calendar day always produces the same session ID, enabling accurate DAU counting server-side.
1 parent e896cce commit e3846c0

File tree

4 files changed

+32
-34
lines changed

4 files changed

+32
-34
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ os_info = "3.9.2"
2323
rand = "0.9.0"
2424
log = "0.4.25"
2525
sys-locale = "0.3.2"
26+
machine-uid = "0.5.4"
27+
sha2 = "0.10.9"
2628

2729
[build-dependencies]
2830
tauri-plugin = { version = "2.0.4", features = ["build"] }

permissions/autogenerated/reference.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
## Permission Table
32

43
<table>

permissions/schemas/schema.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"minimum": 1.0
5050
},
5151
"description": {
52-
"description": "Human-readable description of what the permission does. Tauri convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
52+
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
5353
"type": [
5454
"string",
5555
"null"
@@ -111,7 +111,7 @@
111111
"type": "string"
112112
},
113113
"description": {
114-
"description": "Human-readable description of what the permission does. Tauri internal convention is to use <h4> headings in markdown content for Tauri documentation generation purposes.",
114+
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
115115
"type": [
116116
"string",
117117
"null"
@@ -297,12 +297,14 @@
297297
{
298298
"description": "Enables the track_event command without any pre-configured scope.",
299299
"type": "string",
300-
"const": "allow-track-event"
300+
"const": "allow-track-event",
301+
"markdownDescription": "Enables the track_event command without any pre-configured scope."
301302
},
302303
{
303304
"description": "Denies the track_event command without any pre-configured scope.",
304305
"type": "string",
305-
"const": "deny-track-event"
306+
"const": "deny-track-event",
307+
"markdownDescription": "Denies the track_event command without any pre-configured scope."
306308
}
307309
]
308310
}

src/client.rs

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
use rand::Rng;
21
use serde_json::{json, Value};
3-
use std::time::{SystemTime, UNIX_EPOCH};
4-
use std::{
5-
sync::{Arc, Mutex as SyncMutex},
6-
time::Duration,
7-
};
2+
use sha2::{Digest, Sha256};
3+
use std::sync::{Arc, Mutex as SyncMutex};
4+
use std::time::Duration;
85
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
96

107
use crate::{
@@ -13,41 +10,40 @@ use crate::{
1310
sys::{self, SystemProperties},
1411
};
1512

16-
static SESSION_TIMEOUT: Duration = Duration::from_secs(4 * 60 * 60);
17-
18-
fn new_session_id() -> String {
19-
let epoch_in_seconds = SystemTime::now()
20-
.duration_since(UNIX_EPOCH)
21-
.expect("time went backwards")
22-
.as_secs();
23-
24-
let mut rng = rand::rng();
25-
let random: u64 = rng.random_range(0..=99999999);
13+
/// Derives a deterministic session ID from machine ID, app key, and UTC date.
14+
/// Same device + same app + same calendar day = same session ID.
15+
fn create_session_id(app_key: &str) -> String {
16+
let machine_id = machine_uid::get().unwrap_or_else(|_| "unknown".to_string());
17+
let today = OffsetDateTime::now_utc().date().to_string(); // e.g. "2026-02-13"
2618

27-
let id = epoch_in_seconds * 100_000_000 + random;
19+
let mut hasher = Sha256::new();
20+
hasher.update(machine_id.as_bytes());
21+
hasher.update(app_key.as_bytes());
22+
hasher.update(today.as_bytes());
2823

29-
id.to_string()
24+
format!("{:x}", hasher.finalize())
3025
}
3126

3227
/// A tracking session.
3328
#[derive(Debug, Clone)]
3429
pub struct TrackingSession {
3530
pub id: String,
36-
pub last_touch_ts: OffsetDateTime,
31+
pub date: String,
3732
}
3833

3934
impl TrackingSession {
40-
fn new() -> Self {
35+
fn new(app_key: &str) -> Self {
4136
Self {
42-
id: new_session_id(),
43-
last_touch_ts: OffsetDateTime::now_utc(),
37+
id: create_session_id(app_key),
38+
date: OffsetDateTime::now_utc().date().to_string(),
4439
}
4540
}
4641
}
4742

4843
/// The Aptabase client used to track events.
4944
pub struct AptabaseClient {
5045
is_enabled: bool,
46+
app_key: String,
5147
session: SyncMutex<TrackingSession>,
5248
dispatcher: Arc<EventDispatcher>,
5349
app_version: String,
@@ -64,8 +60,9 @@ impl AptabaseClient {
6460

6561
Self {
6662
is_enabled,
63+
app_key: config.app_key.clone(),
6764
dispatcher,
68-
session: SyncMutex::new(TrackingSession::new()),
65+
session: SyncMutex::new(TrackingSession::new(&config.app_key)),
6966
app_version,
7067
sys_info,
7168
}
@@ -83,15 +80,13 @@ impl AptabaseClient {
8380
});
8481
}
8582

86-
/// Returns the current session ID, creating a new one if necessary.
83+
/// Returns the current session ID, rotating to a new one if the UTC date has changed.
8784
pub(crate) fn eval_session_id(&self) -> String {
8885
let mut session = self.session.lock().expect("could not lock events");
8986

90-
let now = OffsetDateTime::now_utc();
91-
if (now - session.last_touch_ts) > SESSION_TIMEOUT {
92-
*session = TrackingSession::new();
93-
} else {
94-
session.last_touch_ts = now;
87+
let today = OffsetDateTime::now_utc().date().to_string();
88+
if session.date != today {
89+
*session = TrackingSession::new(&self.app_key);
9590
}
9691

9792
session.id.clone()

0 commit comments

Comments
 (0)