-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: add deep-link support and Raycast extension #1708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -32,6 +32,9 @@ pub enum DeepLinkAction { | |||||||||||||
| OpenSettings { | ||||||||||||||
| page: Option<String>, | ||||||||||||||
| }, | ||||||||||||||
| StartDefaultRecording, | ||||||||||||||
| PauseRecording, | ||||||||||||||
| ResumeRecording, | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| pub fn handle(app_handle: &AppHandle, urls: Vec<Url>) { | ||||||||||||||
|
|
@@ -88,6 +91,16 @@ impl TryFrom<&Url> for DeepLinkAction { | |||||||||||||
| .map_err(|_| ActionParseFromUrlError::Invalid); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| if url.scheme() == "cap" { | ||||||||||||||
| return match url.host_str() { | ||||||||||||||
| Some("record") => Ok(Self::StartDefaultRecording), | ||||||||||||||
| Some("stop") => Ok(Self::StopRecording), | ||||||||||||||
| Some("pause") => Ok(Self::PauseRecording), | ||||||||||||||
| Some("resume") => Ok(Self::ResumeRecording), | ||||||||||||||
| _ => Err(ActionParseFromUrlError::Invalid), | ||||||||||||||
| }; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+94
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Registering The existing Consider requiring explicit user-facing confirmation (e.g., showing a toast/alert before acting), or at minimum documenting this behavior so users are aware that any local app can control their recording session via these URLs. Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 94-102
Comment:
**Security: generic `cap://` scheme is accessible by any app or web page**
Registering `cap` as a system-wide URL scheme means any application — including browsers that silently follow `cap://` links in web pages — can trigger `cap://record`, `cap://stop`, `cap://pause`, and `cap://resume` without user confirmation. A malicious web page could embed `<a href="cap://record">` or automatically redirect to it, invisibly starting a screen capture session.
The existing `cap-desktop://action?value=...` scheme has the same surface area, but its JSON-in-query-string format is significantly harder to invoke accidentally or from a web page. The new `cap://` URLs reduce the exploit complexity to a single, guessable link.
Consider requiring explicit user-facing confirmation (e.g., showing a toast/alert before acting), or at minimum documenting this behavior so users are aware that any local app can control their recording session via these URLs.
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||
|
|
||||||||||||||
| match url.domain() { | ||||||||||||||
| Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction), | ||||||||||||||
| _ => Err(ActionParseFromUrlError::Invalid), | ||||||||||||||
|
|
@@ -153,6 +166,15 @@ impl DeepLinkAction { | |||||||||||||
| DeepLinkAction::OpenSettings { page } => { | ||||||||||||||
| crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await | ||||||||||||||
| } | ||||||||||||||
| DeepLinkAction::StartDefaultRecording => { | ||||||||||||||
| app.emit("request-open-recording-picker", ()).map_err(|e| e.to_string()) | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+169
to
+171
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
All other call sites in
Suggested change
You'll also need to import Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 169-171
Comment:
**Wrong event payload causes frontend deserialization failure**
`app.emit("request-open-recording-picker", ())` sends a JSON `null` payload, but the `RequestOpenRecordingPicker` struct has a `target_mode: Option<RecordingTargetMode>` field. The auto-generated frontend type expects `{ target_mode: null }`, not `null`. This payload mismatch will likely cause the event to silently fail or be ignored by the frontend listener.
All other call sites in `hotkeys.rs` correctly use the typed `tauri_specta` event. Use the same pattern here:
```suggestion
DeepLinkAction::StartDefaultRecording => {
RequestOpenRecordingPicker { target_mode: None }.emit(app).map_err(|e| e.to_string())
}
```
You'll also need to import `RequestOpenRecordingPicker` at the top of the file (or reference it as `crate::RequestOpenRecordingPicker`).
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||
| DeepLinkAction::PauseRecording => { | ||||||||||||||
| crate::recording::pause_recording(app.clone(), app.state()).await | ||||||||||||||
| } | ||||||||||||||
| DeepLinkAction::ResumeRecording => { | ||||||||||||||
| crate::recording::resume_recording(app.clone(), app.state()).await | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Host matching is currently case-sensitive, so
cap://Stopetc would be rejected. Might be worth usingeq_ignore_ascii_casehere for resilience.