Skip to content
Merged
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
30 changes: 24 additions & 6 deletions core/feedback/bugReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* are the two things a maintainer always needs to reproduce a bug.
*/

import { isTauri, safeInvoke } from "@/core/bridge/runtime"

const REPO = "https://github.com/harshmathurx/OpenNotes"
const BUG_TEMPLATE = "bug_report.md"

Expand All @@ -14,7 +16,7 @@ function appVersion(): string {
if (typeof process !== "undefined" && process.env?.NEXT_PUBLIC_APP_VERSION) {
return process.env.NEXT_PUBLIC_APP_VERSION
}
return "0.1.0"
return "0.1.1"
}

function platform(): string {
Expand All @@ -27,7 +29,7 @@ function platform(): string {
}

function isDesktopApp(): boolean {
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window
return isTauri()
}

/**
Expand Down Expand Up @@ -59,8 +61,24 @@ export function buildBugReportURL(extra?: { summary?: string }): string {
return `${REPO}/issues/new?${params.toString()}`
}

/** Open the report-a-bug flow in a new tab. No-op in non-browser envs. */
export function openBugReport(): void {
if (typeof window === "undefined") return
window.open(buildBugReportURL(), "_blank", "noopener,noreferrer")
/** Open the report-a-bug flow in the system browser. */
export async function openBugReport(): Promise<boolean> {
if (typeof window === "undefined") return false

const url = buildBugReportURL()

if (isTauri()) {
try {
await safeInvoke("open_external_url", { url })
return true
} catch (error) {
console.warn("[opennotes] Failed to open bug report via Tauri", error)
}
}

const opened = window.open(url, "_blank", "noopener,noreferrer")
if (opened) return true

window.location.assign(url)
return true
}
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ tauri-plugin-dialog = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
keyring = "3"
url = "2"
29 changes: 29 additions & 0 deletions src-tauri/src/feedback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::process::Command;

#[tauri::command]
pub fn open_external_url(url: String) -> Result<(), String> {
let parsed = url::Url::parse(&url).map_err(|_| "Invalid URL".to_string())?;
if parsed.scheme() != "https" {
return Err("Only https URLs can be opened externally".to_string());
}

match parsed.host_str() {
Some("github.com") => {}
_ => return Err("Only GitHub URLs can be opened externally".to_string()),
}

let status = if cfg!(target_os = "macos") {
Command::new("open").arg(&url).status()
} else if cfg!(target_os = "windows") {
Command::new("cmd").args(["/C", "start", "", &url]).status()
} else {
Command::new("xdg-open").arg(&url).status()
}
.map_err(|error| format!("Failed to open URL: {error}"))?;

if status.success() {
Ok(())
} else {
Err(format!("Open command failed with status: {status}"))
}
}
2 changes: 2 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod feedback;
mod fs;
mod git;
mod secrets;
Expand All @@ -7,6 +8,7 @@ pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![
feedback::open_external_url,
git::run_git,
git::git_available,
secrets::set_secret,
Expand Down
36 changes: 34 additions & 2 deletions tests/feedback/bugReport.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { describe, it, expect } from "vitest"
import { buildBugReportURL } from "@/core/feedback/bugReport"
import { beforeEach, describe, it, expect, vi } from "vitest"

const bridge = vi.hoisted(() => ({
safeInvoke: vi.fn(),
tauri: false,
}))

vi.mock("@/core/bridge/runtime", () => ({
isTauri: () => bridge.tauri,
safeInvoke: bridge.safeInvoke,
}))

import { buildBugReportURL, openBugReport } from "@/core/feedback/bugReport"

// URLSearchParams form-encodes spaces as "+"; decode both "+"" and "%20".
function bodyOf(url: string): string {
Expand All @@ -8,6 +19,12 @@ function bodyOf(url: string): string {
}

describe("buildBugReportURL", () => {
beforeEach(() => {
bridge.tauri = false
bridge.safeInvoke.mockReset()
vi.restoreAllMocks()
})

it("points at the repo's new-issue page with the bug template", () => {
const url = buildBugReportURL()
expect(url).toContain("github.com/harshmathurx/OpenNotes/issues/new")
Expand All @@ -25,4 +42,19 @@ describe("buildBugReportURL", () => {
const body = bodyOf(buildBugReportURL({ summary: "sync failed" }))
expect(body).toContain("sync failed")
})

it("opens bug reports through the desktop shell when running in Tauri", async () => {
bridge.tauri = true
bridge.safeInvoke.mockResolvedValue(undefined)
const open = vi.spyOn(window, "open")

await expect(openBugReport()).resolves.toBe(true)

expect(bridge.safeInvoke).toHaveBeenCalledWith("open_external_url", {
url: expect.stringContaining(
"github.com/harshmathurx/OpenNotes/issues/new",
),
})
expect(open).not.toHaveBeenCalled()
})
})
Loading