Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ jobs:
NIXMAC_ENV: prod
NIXMAC_VERSION: ${{ steps.sync-version.outputs.build_version }}
VITE_SENTRY_DSN: ${{ secrets.VITE_SENTRY_DSN }}
VITE_SERVER_URL: ${{ secrets.VITE_SERVER_URL }}
SUBMITTED_FEEDBACK_DSN: ${{ secrets.SUBMITTED_FEEDBACK_DSN }}
VITE_NIXMAC_ENV: prod
VITE_NIXMAC_VERSION: ${{ steps.sync-version.outputs.build_version }}
- name: Unit Test Tauri app
Expand Down
24 changes: 14 additions & 10 deletions apps/native/src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
fn main() {
// If `SENTRY_DSN` is provided in the build environment (set by CI),
// expose it as a compile-time environment variable so it is embedded
// into the binary via `env!` or `option_env!`.
if let Ok(dsn) = std::env::var("SENTRY_DSN") {
println!("cargo:rustc-env=SENTRY_DSN={}", dsn);
}
// Set up passthrough for relevant environment variables.
// This allows configuration to be injected at build time (e.g. by CI)
// or in development environments.
for key in [
"SENTRY_DSN",
"VITE_SERVER_URL",
"SUBMITTED_FEEDBACK_DSN",
"NIXMAC_ENV",
] {
println!("cargo:rerun-if-env-changed={key}");

// If `NIXMAC_ENV` is provided in the build environment, embed it so the
// binary can read it at compile time via `option_env!`.
if let Ok(env) = std::env::var("NIXMAC_ENV") {
println!("cargo:rustc-env=NIXMAC_ENV={}", env);
if let Ok(value) = std::env::var(key) {
println!("cargo:rustc-env={key}={value}");
}
}

// Determine the version to embed. Prefer an explicit `NIXMAC_VERSION`
// from the build environment (e.g. set by CI). If not present, fall
// back to the Cargo package version, and finally to "unknown".
Expand Down
54 changes: 47 additions & 7 deletions apps/native/src-tauri/src/feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,13 @@ fn get_report_path(app: &AppHandle) -> Result<PathBuf> {
/// Submit feedback: try to POST, save to disk on failure, also flush any pending reports.
/// Returns true if the new submission was sent successfully.
pub async fn submit(app: &AppHandle, payload: String) -> Result<bool> {
let feedback_url = get_feedback_url();
let feedback_url = match get_feedback_url() {
Ok(url) => url,
Err(_) => {
log::error!("[feedback] Feedback system is not configured");
return Ok(false);
}
};
let parsed: Value = serde_json::from_str(&payload).unwrap_or(Value::String(payload.clone()));
let client = reqwest::Client::new();

Expand Down Expand Up @@ -549,19 +555,32 @@ fn save_to_queue(app: &AppHandle, payload: &Value, failure_reason: &str) -> Resu
Ok(())
}

const FEEDBACK_DSN: &str = "dsn_6f4b9a5e8c2d4f1a9b3c7e2d5a1f0b4c";

fn get_feedback_url() -> String {
/// Construct the full feedback submission URL from environment configuration.
/// If VITE_SERVER_URL is not set, returns an error indicating feedback is not configured.
/// In a production binary, VITE_SERVER_URL should be embedded at build time;
/// in development it can be set in the environment.
fn get_feedback_url() -> Result<String> {
let base = option_env!("VITE_SERVER_URL")
.map(|s| s.to_string())
.or_else(|| std::env::var("VITE_SERVER_URL").ok())
.unwrap_or_else(|| "http://localhost:3001".to_string());
format!("{}/api/feedback/{}", base, FEEDBACK_DSN)
.ok_or_else(|| anyhow::anyhow!("sending feedback not configured (url)"))?;

let dsn = option_env!("SUBMITTED_FEEDBACK_DSN")
.map(|s| s.to_string())
.or_else(|| std::env::var("SUBMITTED_FEEDBACK_DSN").ok())
.ok_or_else(|| anyhow::anyhow!("sending feedback not configured (dsn)"))?;

Ok(format!("{}/api/feedback/{}", base, dsn))
}

/// Retry all pending feedback reports in the background.
/// Reads report.json, POSTs each entry, removes successes.
pub async fn retry_pending(app: &AppHandle) -> Result<usize> {
let feedback_url = match get_feedback_url() {
Ok(url) => url,
Err(_) => return Ok(0), // If feedback is not configured, skip retrying
};

let path = get_report_path(app)?;

if !path.exists() {
Expand All @@ -577,7 +596,6 @@ pub async fn retry_pending(app: &AppHandle) -> Result<usize> {
return Ok(0);
}

let feedback_url = get_feedback_url();
log::info!(
"[feedback] Found {} pending report(s), sending to {}",
entries.len(),
Expand Down Expand Up @@ -858,4 +876,26 @@ regex = "token=([A-Za-z0-9]+)"
"before [REDACTED (High Entropy)] after"
);
}

#[test]
fn test_get_feedback_url() {
// Backup original env var
let original = std::env::var("VITE_SERVER_URL").ok();

// Test with env var set
std::env::set_var("VITE_SERVER_URL", "https://example.com");
std::env::set_var("SUBMITTED_FEEDBACK_DSN", "test-dsn");
let url = super::get_feedback_url().unwrap();
assert_eq!(url, "https://example.com/api/feedback/test-dsn");

// Test with env var missing
std::env::remove_var("VITE_SERVER_URL");
std::env::remove_var("SUBMITTED_FEEDBACK_DSN");
assert!(super::get_feedback_url().is_err());

// Restore original env var
if let Some(val) = original {
std::env::set_var("VITE_SERVER_URL", val);
}
}
}
Loading