improve home screen

This commit is contained in:
2026-05-06 16:05:04 -06:00
parent 10437c02ca
commit e4484a57f9
9 changed files with 328 additions and 111 deletions

View File

@ -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,

View File

@ -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]