improve home screen
This commit is contained in:
@ -245,6 +245,13 @@ fn get_app_status(app_handle: tauri::AppHandle) -> Result<licensing::AppStatus,
|
||||
Ok(licensing::get_app_status(&data_dir))
|
||||
}
|
||||
|
||||
/// Verify a license key signature without caching. Returns the payload.
|
||||
#[tauri::command]
|
||||
fn verify_license(license_key: String) -> Result<licensing::LicensePayload, String> {
|
||||
licensing::verify_license_key(&license_key)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Verify and activate a license key.
|
||||
#[tauri::command]
|
||||
fn activate_license(app_handle: tauri::AppHandle, license_key: String) -> Result<licensing::LicensePayload, String> {
|
||||
@ -417,6 +424,7 @@ pub fn run() {
|
||||
get_app_status,
|
||||
activate_license,
|
||||
deactivate_license,
|
||||
verify_license,
|
||||
start_trial,
|
||||
has_license_feature,
|
||||
list_models,
|
||||
|
||||
@ -7,7 +7,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
pub const TALKEDIT_PUBLIC_KEY: [u8; 32] = [123, 66, 135, 187, 184, 19, 205, 192, 129, 94, 91, 138, 123, 3, 72, 52, 229, 8, 245, 226, 187, 218, 67, 131, 38, 214, 239, 203, 206, 7, 132, 176];
|
||||
|
||||
pub const TRIAL_DURATION_SECS: u64 = 30 * 86400;
|
||||
pub const TRIAL_DURATION_SECS: u64 = 7 * 86400;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct LicensePayload {
|
||||
@ -25,6 +25,13 @@ pub struct TrialState {
|
||||
pub started_at: u64,
|
||||
}
|
||||
|
||||
/// On-disk format with integrity seed to deter tampering.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TrialFile {
|
||||
started_at: u64,
|
||||
seed: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(tag = "tag")]
|
||||
pub enum AppStatus {
|
||||
@ -113,6 +120,52 @@ pub fn trial_file_path(app_data_dir: &PathBuf) -> PathBuf {
|
||||
app_data_dir.join("trial.json")
|
||||
}
|
||||
|
||||
pub fn trial_sentinel_path(app_data_dir: &PathBuf) -> PathBuf {
|
||||
app_data_dir.join(".trial_lock")
|
||||
}
|
||||
|
||||
// Simple integrity check constant — not crypto-grade, but deters casual editing.
|
||||
const TRIAL_SEED: u64 = 0x9F3A_2E7D_C1B8_5604;
|
||||
|
||||
pub fn get_or_start_trial(app_data_dir: &PathBuf) -> TrialState {
|
||||
let path = trial_file_path(app_data_dir);
|
||||
let sentinel = trial_sentinel_path(app_data_dir);
|
||||
let now = now_secs();
|
||||
|
||||
// If sentinel exists but trial was deleted, refuse to create a new one.
|
||||
let sentinel_exists = sentinel.exists();
|
||||
|
||||
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||
if let Ok(wrapped) = serde_json::from_str::<TrialFile>(&content) {
|
||||
// Verify integrity
|
||||
if (wrapped.started_at ^ TRIAL_SEED) == wrapped.seed {
|
||||
return TrialState { started_at: wrapped.started_at };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sentinel_exists {
|
||||
// Trial was tampered with — return an expired trial
|
||||
return TrialState { started_at: 0 };
|
||||
}
|
||||
|
||||
// Start new trial
|
||||
let trial = TrialState { started_at: now };
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
let _ = std::fs::write(&sentinel, "1");
|
||||
let _ = std::fs::write(
|
||||
&path,
|
||||
serde_json::to_string(&TrialFile {
|
||||
started_at: trial.started_at,
|
||||
seed: trial.started_at ^ TRIAL_SEED,
|
||||
})
|
||||
.unwrap(),
|
||||
);
|
||||
trial
|
||||
}
|
||||
|
||||
pub fn load_cached_license(app_data_dir: &PathBuf) -> Result<LicensePayload, LicenseError> {
|
||||
let path = license_file_path(app_data_dir);
|
||||
let content = std::fs::read_to_string(&path).map_err(|_| LicenseError::InvalidFormat)?;
|
||||
@ -136,26 +189,6 @@ pub fn remove_license(app_data_dir: &PathBuf) {
|
||||
|
||||
// --- Trial logic ---
|
||||
|
||||
pub fn get_or_start_trial(app_data_dir: &PathBuf) -> TrialState {
|
||||
let path = trial_file_path(app_data_dir);
|
||||
let now = now_secs();
|
||||
|
||||
// Try to load existing trial
|
||||
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||
if let Ok(trial) = serde_json::from_str::<TrialState>(&content) {
|
||||
return trial;
|
||||
}
|
||||
}
|
||||
|
||||
// Start new trial
|
||||
let trial = TrialState { started_at: now };
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
let _ = std::fs::write(&path, serde_json::to_string(&trial).unwrap());
|
||||
trial
|
||||
}
|
||||
|
||||
pub fn get_trial_info(trial: &TrialState) -> (u64, u64, bool) {
|
||||
let now = now_secs();
|
||||
let elapsed = now.saturating_sub(trial.started_at);
|
||||
@ -176,8 +209,8 @@ pub fn get_trial_days_remaining(trial: &TrialState) -> Option<u32> {
|
||||
}
|
||||
|
||||
pub fn clear_trial(app_data_dir: &PathBuf) {
|
||||
let path = trial_file_path(app_data_dir);
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let _ = std::fs::remove_file(trial_file_path(app_data_dir));
|
||||
let _ = std::fs::remove_file(trial_sentinel_path(app_data_dir));
|
||||
}
|
||||
|
||||
/// Get the overall app status: Licensed > Trial > Expired.
|
||||
@ -248,7 +281,7 @@ mod tests {
|
||||
let trial = TrialState { started_at: now_secs() };
|
||||
let days = get_trial_days_remaining(&trial);
|
||||
assert!(days.is_some());
|
||||
assert_eq!(days.unwrap(), 30);
|
||||
assert_eq!(days.unwrap(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user