offline_protocol/telemetry/pipe/host.rs
1//! What the host tells the pipe about itself: the platform every batch
2//! envelope names, and the application lifecycle the session boundaries
3//! follow.
4//!
5//! The binding fills these in, never the application: an `os` the app could
6//! set is an `os` the app could set wrong, and the ingest keys its
7//! per-platform aggregates on it.
8
9use offline_protocol_telemetry_wire::OsKind;
10
11/// The operating system the SDK is running on, as the ingest names it.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum TelemetryOs {
14 /// Apple iOS.
15 Ios,
16 /// Android.
17 Android,
18 /// Linux.
19 Linux,
20 /// Apple macOS.
21 Macos,
22 /// Microsoft Windows.
23 Windows,
24 /// A host the binding could not classify.
25 Other,
26}
27
28impl TelemetryOs {
29 /// The host this binary was compiled for, with no version information.
30 ///
31 /// The bindings pass the real major version alongside; this is the
32 /// fallback for a Rust embedder that has nothing better to say.
33 pub fn current() -> Self {
34 if cfg!(target_os = "ios") {
35 Self::Ios
36 } else if cfg!(target_os = "android") {
37 Self::Android
38 } else if cfg!(target_os = "linux") {
39 Self::Linux
40 } else if cfg!(target_os = "macos") {
41 Self::Macos
42 } else if cfg!(target_os = "windows") {
43 Self::Windows
44 } else {
45 Self::Other
46 }
47 }
48
49 pub(crate) fn wire(self) -> OsKind {
50 match self {
51 Self::Ios => OsKind::Ios,
52 Self::Android => OsKind::Android,
53 Self::Linux => OsKind::Linux,
54 Self::Macos => OsKind::Macos,
55 Self::Windows => OsKind::Windows,
56 Self::Other => OsKind::Other,
57 }
58 }
59
60 pub(crate) fn user_agent_token(self) -> &'static str {
61 match self {
62 Self::Ios => "ios",
63 Self::Android => "android",
64 Self::Linux => "linux",
65 Self::Macos => "macos",
66 Self::Windows => "windows",
67 Self::Other => "other",
68 }
69 }
70}
71
72/// The application lifecycle states the pipe draws session boundaries from.
73///
74/// `Background` closes a session (summary, then a flush); the first `Active`
75/// after a `Background` opens the next one. `Inactive` is the transient state
76/// iOS reports around a notification or an incoming call and is ignored: it
77/// is not a boundary a user would recognise as leaving the app.
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum AppState {
80 /// The application is in the foreground.
81 Active,
82 /// The application has left the foreground.
83 Background,
84 /// A transient foreground interruption; ignored.
85 Inactive,
86}
87
88/// Host facts supplied at `enable_telemetry`.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct TelemetryHost {
91 /// The host operating system.
92 pub os: TelemetryOs,
93 /// Its major version (`18` for iOS 18.x; the API level on Android).
94 pub os_major: u16,
95 /// The application state at the moment telemetry is enabled, so a pipe
96 /// enabled by a background launch closes its first session on the next
97 /// foreground rather than waiting for a round trip it may never see.
98 pub app_state: AppState,
99}
100
101impl TelemetryHost {
102 /// A host that is active right now.
103 pub fn new(os: TelemetryOs, os_major: u16) -> Self {
104 Self {
105 os,
106 os_major,
107 app_state: AppState::Active,
108 }
109 }
110
111 /// Sets the application state at enable time.
112 pub fn with_app_state(mut self, app_state: AppState) -> Self {
113 self.app_state = app_state;
114 self
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn every_os_has_a_wire_form_and_a_user_agent_token() {
124 for os in [
125 TelemetryOs::Ios,
126 TelemetryOs::Android,
127 TelemetryOs::Linux,
128 TelemetryOs::Macos,
129 TelemetryOs::Windows,
130 TelemetryOs::Other,
131 ] {
132 let wire = serde_json::to_value(os.wire()).expect("serializes");
133 assert_eq!(
134 wire,
135 serde_json::Value::String(os.user_agent_token().into())
136 );
137 }
138 }
139
140 #[test]
141 fn current_platform_is_one_of_the_named_hosts() {
142 // On the machines this suite runs on, the answer is never `Other`.
143 assert_ne!(TelemetryOs::current(), TelemetryOs::Other);
144 }
145}