Skip to main content

tauri_plugin_licensemanager/core/
state.rs

1//! Port of `license-state.ts`.
2
3use std::time::{Duration, SystemTime};
4
5use serde::{Deserialize, Serialize};
6
7use super::error::LicenseErrorCode;
8
9/// The license state of the app.
10#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
12pub enum LicenseState {
13    /// Startup validation in progress.
14    Loading,
15    /// No license key stored yet: ask the user for one.
16    MissingKey,
17    /// Valid license, verified online or with the stored token.
18    Active,
19    /// The key does not exist for this product.
20    InvalidKey,
21    /// The license expiration date has passed.
22    Expired,
23    /// The license doesn't include this app version. See
24    /// [`LicenseManager::latest_allowed_version`](crate::LicenseManager::latest_allowed_version).
25    VersionNotAllowed,
26    /// The license is active on its maximum number of machines.
27    MachineLimitReached,
28    /// Server unreachable and no valid stored token.
29    OfflineExpired,
30    /// Server unreachable and the system clock was rolled back.
31    ClockTampered,
32}
33
34impl LicenseState {
35    /// The wire string, e.g. `"ACTIVE"`.
36    pub fn as_str(self) -> &'static str {
37        match self {
38            Self::Loading => "LOADING",
39            Self::MissingKey => "MISSING_KEY",
40            Self::Active => "ACTIVE",
41            Self::InvalidKey => "INVALID_KEY",
42            Self::Expired => "EXPIRED",
43            Self::VersionNotAllowed => "VERSION_NOT_ALLOWED",
44            Self::MachineLimitReached => "MACHINE_LIMIT_REACHED",
45            Self::OfflineExpired => "OFFLINE_EXPIRED",
46            Self::ClockTampered => "CLOCK_TAMPERED",
47        }
48    }
49}
50
51impl std::fmt::Display for LicenseState {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.write_str(self.as_str())
54    }
55}
56
57/// Whether the last validation reached the server.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum OfflineState {
60    /// The last validation reached the server.
61    Online,
62    /// The server could not be reached: the app runs on the stored token.
63    Offline {
64        /// Expiry of the stored token, or `None` when there is no valid token.
65        valid_until: Option<SystemTime>,
66        /// Time left before `valid_until`; zero without a valid token.
67        remaining: Duration,
68    },
69}
70
71/// State the app locks into when the server refuses the license
72/// (`LOCKED_STATES`). `None` for non-locking codes.
73pub(crate) fn locked_state(code: LicenseErrorCode) -> Option<LicenseState> {
74    match code {
75        LicenseErrorCode::NotFound => Some(LicenseState::InvalidKey),
76        LicenseErrorCode::LicenseExpired => Some(LicenseState::Expired),
77        LicenseErrorCode::VersionNotAllowed => Some(LicenseState::VersionNotAllowed),
78        LicenseErrorCode::MachineLimitReached => Some(LicenseState::MachineLimitReached),
79        _ => None,
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn serializes_states_like_electron() {
89        for state in [
90            LicenseState::Loading,
91            LicenseState::MissingKey,
92            LicenseState::Active,
93            LicenseState::InvalidKey,
94            LicenseState::Expired,
95            LicenseState::VersionNotAllowed,
96            LicenseState::MachineLimitReached,
97            LicenseState::OfflineExpired,
98            LicenseState::ClockTampered,
99        ] {
100            assert_eq!(
101                serde_json::to_string(&state).unwrap(),
102                format!("\"{state}\"")
103            );
104        }
105    }
106
107    #[test]
108    fn locked_states() {
109        assert_eq!(
110            locked_state(LicenseErrorCode::NotFound),
111            Some(LicenseState::InvalidKey)
112        );
113        assert_eq!(
114            locked_state(LicenseErrorCode::LicenseExpired),
115            Some(LicenseState::Expired)
116        );
117        assert_eq!(
118            locked_state(LicenseErrorCode::VersionNotAllowed),
119            Some(LicenseState::VersionNotAllowed)
120        );
121        assert_eq!(
122            locked_state(LicenseErrorCode::MachineLimitReached),
123            Some(LicenseState::MachineLimitReached)
124        );
125        assert_eq!(locked_state(LicenseErrorCode::NetworkError), None);
126    }
127}