-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add deeplink support for pause, resume, mic and camera switching (#1540) #2051
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
332beac
8e9fb90
691fe87
cb0ea6d
2479097
2c45cd1
1a5b943
5509218
bf82662
d1230f4
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 |
|---|---|---|
|
|
@@ -46,17 +46,16 @@ pub enum DeepLinkAction { | |
| mode: RecordingMode, | ||
| }, | ||
| StopRecording, | ||
| #[cfg(debug_assertions)] | ||
| PauseRecording, | ||
| #[cfg(debug_assertions)] | ||
| ResumeRecording, | ||
| #[cfg(debug_assertions)] | ||
| SwitchMicrophone { | ||
| mic_label: Option<String>, | ||
| }, | ||
| OpenCamera { | ||
| camera: DeviceOrModelID, | ||
| }, | ||
| #[cfg(debug_assertions)] | ||
| SetCameraPreviewState { | ||
| state: CameraPreviewState, | ||
| state: crate::camera::CameraPreviewState, | ||
|
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. Minor CI/clippy thing: now that this uses 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. Now that the enum uses |
||
| }, | ||
| OpenEditor { | ||
| project_path: PathBuf, | ||
|
|
@@ -164,21 +163,51 @@ impl TryFrom<&Url> for DeepLinkAction { | |
| .map_err(|_| ActionParseFromUrlError::Invalid); | ||
| } | ||
|
|
||
| match url.domain() { | ||
| Some("action") => {} | ||
| Some(_) => return Err(ActionParseFromUrlError::NotAction), | ||
| None => return Err(ActionParseFromUrlError::Invalid), | ||
| if !matches!(url.scheme(), "cap" | "cap-desktop") { | ||
|
BMNTR marked this conversation as resolved.
BMNTR marked this conversation as resolved.
|
||
| return Err(ActionParseFromUrlError::NotAction); | ||
| } | ||
|
|
||
| let params = url | ||
| .query_pairs() | ||
| .collect::<std::collections::HashMap<_, _>>(); | ||
| let json_value = params | ||
| .get("value") | ||
| .ok_or(ActionParseFromUrlError::Invalid)?; | ||
| let action: Self = serde_json::from_str(json_value) | ||
| .map_err(|e| ActionParseFromUrlError::ParseFailed(e.to_string()))?; | ||
| Ok(action) | ||
| match url.host_str() { | ||
|
BMNTR marked this conversation as resolved.
|
||
| Some("action") => { | ||
| let params = url | ||
| .query_pairs() | ||
| .collect::<std::collections::HashMap<_, _>>(); | ||
| let json_value = params | ||
| .get("value") | ||
| .ok_or(ActionParseFromUrlError::Invalid)?; | ||
| let action: Self = serde_json::from_str(json_value) | ||
| .map_err(|e| ActionParseFromUrlError::ParseFailed(e.to_string()))?; | ||
| Ok(action) | ||
| } | ||
|
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. P1: Sensitive recording controls exposed in production via unauthenticated deep links Sensitive recording controls now exposed via unauthenticated deep links in production builds. Add authorization checks or user confirmation before executing sensitive controls via deep links. AI prompt |
||
| Some("stop" | "stop-recording") => Ok(Self::StopRecording), | ||
| Some("pause" | "pause-recording") => Ok(Self::PauseRecording), | ||
| Some("resume" | "resume-recording") => Ok(Self::ResumeRecording), | ||
| Some("switch-mic" | "switch-microphone") => { | ||
| let params = url | ||
| .query_pairs() | ||
| .collect::<std::collections::HashMap<_, _>>(); | ||
| let mic_label = params | ||
|
BMNTR marked this conversation as resolved.
|
||
| .get("mic_label") | ||
| .or_else(|| params.get("label")) | ||
| .and_then(|s| (!s.is_empty()).then(|| s.to_string())); | ||
| Ok(Self::SwitchMicrophone { mic_label }) | ||
| } | ||
| Some("open-camera" | "switch-camera") => { | ||
| let params = url | ||
| .query_pairs() | ||
| .collect::<std::collections::HashMap<_, _>>(); | ||
| let device_id = params | ||
|
BMNTR marked this conversation as resolved.
|
||
| .get("id") | ||
| .or_else(|| params.get("camera")) | ||
| .and_then(|s| (!s.is_empty()).then(|| s.to_string())) | ||
| .ok_or(ActionParseFromUrlError::Invalid)?; | ||
| Ok(Self::OpenCamera { | ||
| camera: DeviceOrModelID::DeviceID(device_id), | ||
| }) | ||
| } | ||
| Some(_) => Err(ActionParseFromUrlError::NotAction), | ||
| None => Err(ActionParseFromUrlError::Invalid), | ||
| } | ||
|
BMNTR marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
|
|
@@ -244,15 +273,18 @@ impl DeepLinkAction { | |
| DeepLinkAction::StopRecording => { | ||
| crate::recording::stop_recording(app.clone(), app.state()).await | ||
| } | ||
| #[cfg(debug_assertions)] | ||
| DeepLinkAction::PauseRecording => { | ||
| crate::recording::pause_recording(app.clone(), app.state()).await | ||
| } | ||
| #[cfg(debug_assertions)] | ||
| DeepLinkAction::ResumeRecording => { | ||
| crate::recording::resume_recording(app.clone(), app.state()).await | ||
| } | ||
| #[cfg(debug_assertions)] | ||
| DeepLinkAction::SwitchMicrophone { mic_label } => { | ||
| let state = app.state::<ArcLock<App>>(); | ||
| crate::set_mic_input(state, mic_label) | ||
| .await | ||
| .map_err(|e| e.to_string()) | ||
| } | ||
| DeepLinkAction::OpenCamera { camera } => { | ||
|
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. Since this arm is no longer 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. Since |
||
| crate::set_camera_input( | ||
| app.clone(), | ||
|
|
@@ -282,7 +314,6 @@ impl DeepLinkAction { | |
|
|
||
| Ok(()) | ||
| } | ||
| #[cfg(debug_assertions)] | ||
| DeepLinkAction::SetCameraPreviewState { state } => { | ||
| crate::set_camera_preview_state(app.state(), state).await | ||
| } | ||
|
|
@@ -358,7 +389,6 @@ mod tests { | |
| ); | ||
| } | ||
|
|
||
| #[cfg(debug_assertions)] | ||
| #[test] | ||
|
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. Since the new public contract is the direct host routes (e.g. |
||
| fn parses_pause_and_resume_action_urls() { | ||
| let pause_url = Url::parse("cap-desktop://action?value=%22pause_recording%22").unwrap(); | ||
|
|
@@ -374,6 +404,35 @@ mod tests { | |
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn parses_direct_scheme_urls() { | ||
| let pause_url = Url::parse("cap://pause").unwrap(); | ||
| let resume_url = Url::parse("cap://resume").unwrap(); | ||
| let mic_url = Url::parse("cap://switch-mic?label=Shure%20MV7%2B").unwrap(); | ||
| let camera_url = Url::parse("cap://switch-camera?id=cam-1").unwrap(); | ||
|
|
||
| assert_eq!( | ||
| DeepLinkAction::try_from(&pause_url), | ||
| Ok(DeepLinkAction::PauseRecording) | ||
| ); | ||
| assert_eq!( | ||
| DeepLinkAction::try_from(&resume_url), | ||
| Ok(DeepLinkAction::ResumeRecording) | ||
| ); | ||
| assert_eq!( | ||
| DeepLinkAction::try_from(&mic_url), | ||
| Ok(DeepLinkAction::SwitchMicrophone { | ||
| mic_label: Some("Shure MV7+".to_string()) | ||
| }) | ||
| ); | ||
| assert_eq!( | ||
| DeepLinkAction::try_from(&camera_url), | ||
| Ok(DeepLinkAction::OpenCamera { | ||
| camera: DeviceOrModelID::DeviceID("cam-1".to_string()) | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| #[cfg(debug_assertions)] | ||
| #[test] | ||
| fn parses_area_recording_action_url() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,7 +36,7 @@ | |
| "updater": { "active": false, "pubkey": "" }, | ||
| "deep-link": { | ||
| "desktop": { | ||
| "schemes": ["cap-desktop"] | ||
| "schemes": ["cap-desktop", "cap"] | ||
|
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. Just sanity-checking production: this repo also has |
||
| } | ||
| } | ||
| }, | ||
|
|
||
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.
impl TryFrom<&Url> for DeepLinkActionlooks accidentally corrupted right now (multiple nestedfn try_from(...)declarations + strayreturn Err(...)blocks). As-is it won’t compile.I’d collapse it back to a single
fn try_from(url: &Url) -> Result<...>body (keeping the macOSfile://early return) and then keep the scheme/host routing logic inside that one function.