From 85674a5ef2536392ff5d9fb319de1de5e3f1953d Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Thu, 2 Oct 2025 21:32:35 +1000 Subject: [PATCH 1/7] wip --- Cargo.lock | 1 + apps/desktop/src-tauri/Cargo.toml | 1 + apps/desktop/src-tauri/src/lib.rs | 24 +++++----- apps/desktop/src-tauri/src/recording.rs | 2 +- apps/desktop/src-tauri/src/upload.rs | 58 ++++++++++--------------- apps/desktop/src-tauri/src/web_api.rs | 54 ++++++++++++++++------- 6 files changed, 77 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f97531278..ad187a33ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1220,6 +1220,7 @@ dependencies = [ "tauri-plugin-window-state", "tauri-specta", "tempfile", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 85046ece21..d17a175871 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -105,6 +105,7 @@ wgpu.workspace = true bytemuck = "1.23.1" kameo = "0.17.2" tauri-plugin-sentry = "0.5.0" +thiserror.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.24.0" diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 7012426542..cab3b7644a 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -82,6 +82,7 @@ use tauri_specta::Event; use tokio::sync::{RwLock, oneshot}; use tracing::{error, trace}; use upload::{S3UploadMeta, create_or_get_video, upload_image, upload_video}; +use web_api::AuthedApiError; use web_api::ManagerExt as WebManagerExt; use windows::{CapWindowId, EditorWindowIds, ShowCapWindow, set_window_transparent}; @@ -1077,7 +1078,7 @@ async fn upload_exported_video( channel.send(UploadProgress { progress: 0.0 }).ok(); - let s3_config = async { + let s3_config = match async { let video_id = match mode { UploadMode::Initial { pre_created_video } => { if let Some(pre_created) = pre_created_video { @@ -1087,7 +1088,7 @@ async fn upload_exported_video( } UploadMode::Reupload => { let Some(sharing) = meta.sharing.clone() else { - return Err("No sharing metadata found".to_string()); + return Err("No sharing metadata found".into()); }; Some(sharing.id) @@ -1103,7 +1104,13 @@ async fn upload_exported_video( ) .await } - .await?; + .await + { + Ok(data) => data, + Err(AuthedApiError::InvalidAuthentication) => return Ok(UploadResult::NotAuthenticated), + Err(AuthedApiError::UpgradeRequired) => return Ok(UploadResult::UpgradeRequired), + Err(err) => return Err(err.to_string()), + }; let upload_id = s3_config.id().to_string(); @@ -1136,11 +1143,12 @@ async fn upload_exported_video( NotificationType::ShareableLinkCopied.send(&app); Ok(UploadResult::Success(uploaded_video.link)) } + Err(AuthedApiError::UpgradeRequired) => Ok(UploadResult::UpgradeRequired), Err(e) => { error!("Failed to upload video: {e}"); NotificationType::UploadFailed.send(&app); - Err(e) + Err(e.to_string().into()) } } } @@ -1547,16 +1555,10 @@ async fn check_upgraded_and_update(app: AppHandle) -> Result { .await .map_err(|e| { println!("Failed to fetch plan: {e}"); - format!("Failed to fetch plan: {e}") + e.to_string() })?; println!("Plan fetch response status: {}", response.status()); - if response.status() == reqwest::StatusCode::UNAUTHORIZED { - println!("Unauthorized response, clearing auth store"); - AuthStore::set(&app, None).map_err(|e| e.to_string())?; - return Ok(false); - } - let plan_data = response.json::().await.map_err(|e| { println!("Failed to parse plan response: {e}"); format!("Failed to parse plan response: {e}") diff --git a/apps/desktop/src-tauri/src/recording.rs b/apps/desktop/src-tauri/src/recording.rs index ba52090744..083a73ffcc 100644 --- a/apps/desktop/src-tauri/src/recording.rs +++ b/apps/desktop/src-tauri/src/recording.rs @@ -265,7 +265,7 @@ fn normalize_thumbnail_dimensions(image: &image::RgbaImage) -> image::RgbaImage async fn capture_thumbnail_from_filter(filter: &cidre::sc::ContentFilter) -> Option { use cidre::{cv, sc}; use image::{ImageEncoder, RgbaImage, codecs::png::PngEncoder}; - use std::{io::Cursor, slice}; + use std::io::Cursor; let mut config = sc::StreamCfg::new(); config.set_width(THUMBNAIL_WIDTH as usize); diff --git a/apps/desktop/src-tauri/src/upload.rs b/apps/desktop/src-tauri/src/upload.rs index f6e9236972..bc35604a86 100644 --- a/apps/desktop/src-tauri/src/upload.rs +++ b/apps/desktop/src-tauri/src/upload.rs @@ -1,6 +1,6 @@ // credit @filleduchaos -use crate::web_api::ManagerExt; +use crate::web_api::{AuthedApiError, ManagerExt}; use crate::{UploadProgress, VideoUploadInfo}; use cap_utils::spawn_actor; use ffmpeg::ffi::AV_TIME_BASE; @@ -216,7 +216,7 @@ pub async fn upload_video( screenshot_path: Option, meta: Option, channel: Option>, -) -> Result { +) -> Result { println!("Uploading video {video_id}..."); let client = reqwest::Client::new(); @@ -284,7 +284,7 @@ pub async fn upload_video( let (video_upload, screenshot_result): ( Result, - Option>, + Option>, ) = tokio::join!(video_upload.send(), async { if let Some(screenshot_req) = screenshot_upload { Some(screenshot_req.await) @@ -326,12 +326,13 @@ pub async fn upload_video( status, error_body ); - Err(format!( - "Failed to upload file. Status: {status}. Body: {error_body}" - )) + Err(format!("Failed to upload file. Status: {status}. Body: {error_body}").into()) } -pub async fn upload_image(app: &AppHandle, file_path: PathBuf) -> Result { +pub async fn upload_image( + app: &AppHandle, + file_path: PathBuf, +) -> Result { let file_name = file_path .file_name() .and_then(|name| name.to_str()) @@ -382,9 +383,7 @@ pub async fn upload_image(app: &AppHandle, file_path: PathBuf) -> Result, name: Option, meta: Option, -) -> Result { +) -> Result { + return Err(AuthedApiError::InvalidAuthentication); // TODO + let mut s3_config_url = if let Some(id) = video_id { format!("/api/desktop/video/create?recordingMode=desktopMP4&videoId={id}") } else if is_screenshot { @@ -417,23 +418,13 @@ pub async fn create_or_get_video( let response = app .authed_api_request(s3_config_url, |client, url| client.get(url)) - .await - .map_err(|e| format!("Failed to send request to Next.js handler: {e}"))?; - - if response.status() == StatusCode::UNAUTHORIZED { - return Err("Failed to authenticate request; please log in again".into()); - } + .await?; if response.status() != StatusCode::OK { if let Ok(error) = response.json::().await { if error.error == "upgrade_required" { - return Err( - "You must upgrade to Cap Pro to upload recordings over 5 minutes in length" - .into(), - ); + return Err(AuthedApiError::UpgradeRequired); } - - return Err(format!("server error: {}", error.error)); } return Err("Unknown error uploading video".into()); @@ -469,7 +460,10 @@ pub enum PresignedS3PutRequestMethod { Put, } -async fn presigned_s3_put(app: &AppHandle, body: PresignedS3PutRequest) -> Result { +async fn presigned_s3_put( + app: &AppHandle, + body: PresignedS3PutRequest, +) -> Result { #[derive(Deserialize, Debug)] struct Data { url: String, @@ -485,17 +479,9 @@ async fn presigned_s3_put(app: &AppHandle, body: PresignedS3PutRequest) -> Resul .authed_api_request("/api/upload/signed", |client, url| { client.post(url).json(&body) }) - .await - .map_err(|e| format!("Failed to send request to Next.js handler: {e}"))?; + .await?; - if response.status() == StatusCode::UNAUTHORIZED { - return Err("Failed to authenticate request; please log in again".into()); - } - - let Wrapper { presigned_put_data } = response - .json::() - .await - .map_err(|e| format!("Failed to deserialize server response: {e}"))?; + let Wrapper { presigned_put_data } = response.json::().await?; Ok(presigned_put_data.url) } @@ -556,7 +542,7 @@ pub async fn prepare_screenshot_upload( app: &AppHandle, s3_config: &S3UploadMeta, screenshot_path: PathBuf, -) -> Result { +) -> Result { let presigned_put = presigned_s3_put( app, PresignedS3PutRequest { @@ -576,7 +562,7 @@ pub async fn prepare_screenshot_upload( .body(compressed_image) .send() .await - .map_err(|e| format!("Error uploading screenshot: {e}")) + .map_err(Into::into) } async fn compress_image(path: PathBuf) -> Result, String> { diff --git a/apps/desktop/src-tauri/src/web_api.rs b/apps/desktop/src-tauri/src/web_api.rs index 2970d903fb..76238b7b17 100644 --- a/apps/desktop/src-tauri/src/web_api.rs +++ b/apps/desktop/src-tauri/src/web_api.rs @@ -1,13 +1,42 @@ use reqwest::StatusCode; use tauri::{Emitter, Manager, Runtime}; use tauri_specta::Event; -use tracing::error; +use thiserror::Error; +use tracing::{error, warn}; use crate::{ ArcLock, auth::{AuthSecret, AuthStore, AuthenticationInvalid}, }; +#[derive(Error, Debug)] +pub enum AuthedApiError { + #[error("User is not authenticated or credentials have expired!")] + InvalidAuthentication, + #[error("User needs to upgrade their account to use this feature!")] + UpgradeRequired, + #[error("AuthedApiError/AuthStore: {0}")] + AuthStore(String), + #[error("AuthedApiError/Request: {0}")] + Request(#[from] reqwest::Error), + #[error("AuthedApiError/Deserialization: {0}")] + Deserialization(#[from] serde_json::Error), + #[error("AuthedApiError/Other: {0}")] + Other(String), +} + +impl From<&'static str> for AuthedApiError { + fn from(value: &'static str) -> Self { + AuthedApiError::Other(value.into()) + } +} + +impl From for AuthedApiError { + fn from(value: String) -> Self { + AuthedApiError::Other(value) + } +} + async fn do_authed_request( auth: &AuthStore, build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder, @@ -28,7 +57,7 @@ async fn do_authed_request( ) .header("X-Desktop-Version", env!("CARGO_PKG_VERSION")); - if let Some(s) = std::option_env!("VITE_VERCEL_AUTOMATION_BYPASS_SECRET") { + if let Ok(s) = std::env::var("VITE_VERCEL_AUTOMATION_BYPASS_SECRET") { req = req.header("x-vercel-protection-bypass", s); } @@ -40,7 +69,7 @@ pub trait ManagerExt: Manager { &self, path: impl Into, build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder, - ) -> Result; + ) -> Result; async fn make_app_url(&self, pathname: impl AsRef) -> String; } @@ -50,26 +79,21 @@ impl + Emitter, R: Runtime> ManagerExt for T { &self, path: impl Into, build: impl FnOnce(reqwest::Client, String) -> reqwest::RequestBuilder, - ) -> Result { - let Some(auth) = AuthStore::get(self.app_handle())? else { - println!("Not logged in"); - + ) -> Result { + let Some(auth) = AuthStore::get(self.app_handle()).map_err(AuthedApiError::AuthStore)? + else { + warn!("Not logged in"); AuthenticationInvalid.emit(self).ok(); - - return Err("Unauthorized".to_string()); + return Err(AuthedApiError::InvalidAuthentication); }; let url = self.make_app_url(path.into()).await; - let response = do_authed_request(&auth, build, url) - .await - .map_err(|e| e.to_string())?; + let response = do_authed_request(&auth, build, url).await?; if response.status() == StatusCode::UNAUTHORIZED { error!("Authentication expired. Please log in again."); - AuthenticationInvalid.emit(self).ok(); - - return Err("Unauthorized".to_string()); + return Err(AuthedApiError::InvalidAuthentication); } Ok(response) From 1c2dac28cb7f8201e4984d201b065a45397ed092 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 13 Oct 2025 12:24:39 +0800 Subject: [PATCH 2/7] fix merge conflict --- apps/desktop/src-tauri/src/upload.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/desktop/src-tauri/src/upload.rs b/apps/desktop/src-tauri/src/upload.rs index 11a5991324..7994c33f21 100644 --- a/apps/desktop/src-tauri/src/upload.rs +++ b/apps/desktop/src-tauri/src/upload.rs @@ -63,11 +63,8 @@ pub async fn upload_video( screenshot_path: PathBuf, meta: S3VideoMeta, channel: Option>, -<<<<<<< HEAD -) -> Result { +) -> Result { println!("Uploading video {video_id}..."); -======= -) -> Result { let is_new_uploader_enabled = GeneralSettingsStore::get(&app) .map_err(|err| error!("Error checking status of new uploader flow from settings: {err}")) .ok() From ebf3714922319de06c8e8968f6dd7f91b4155cf4 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 13 Oct 2025 13:36:49 +0800 Subject: [PATCH 3/7] wip --- apps/desktop/src-tauri/src/api.rs | 18 ++++++++++++------ apps/desktop/src-tauri/src/auth.rs | 3 --- apps/desktop/src-tauri/src/lib.rs | 3 +-- apps/desktop/src-tauri/src/recording.rs | 3 ++- apps/desktop/src-tauri/src/upload.rs | 1 - apps/desktop/src-tauri/src/web_api.rs | 4 +--- .../src/routes/(window-chrome)/(main).tsx | 15 ++++++++++----- .../src/routes/target-select-overlay.tsx | 16 +++++++++++----- 8 files changed, 37 insertions(+), 26 deletions(-) diff --git a/apps/desktop/src-tauri/src/api.rs b/apps/desktop/src-tauri/src/api.rs index 92a2ba2030..c8467ad304 100644 --- a/apps/desktop/src-tauri/src/api.rs +++ b/apps/desktop/src-tauri/src/api.rs @@ -5,9 +5,12 @@ use serde::{Deserialize, Serialize}; use serde_json::json; use tauri::AppHandle; -use crate::web_api::ManagerExt; +use crate::web_api::{AuthedApiError, ManagerExt}; -pub async fn upload_multipart_initiate(app: &AppHandle, video_id: &str) -> Result { +pub async fn upload_multipart_initiate( + app: &AppHandle, + video_id: &str, +) -> Result { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct Response { @@ -49,7 +52,7 @@ pub async fn upload_multipart_presign_part( upload_id: &str, part_number: u32, md5_sum: &str, -) -> Result { +) -> Result { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct Response { @@ -114,7 +117,7 @@ pub async fn upload_multipart_complete( upload_id: &str, parts: &[UploadedPart], meta: Option, -) -> Result, String> { +) -> Result, AuthedApiError> { #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct MultipartCompleteRequest<'a> { @@ -179,7 +182,10 @@ pub struct PresignedS3PutRequest { pub meta: Option, } -pub async fn upload_signed(app: &AppHandle, body: PresignedS3PutRequest) -> Result { +pub async fn upload_signed( + app: &AppHandle, + body: PresignedS3PutRequest, +) -> Result { #[derive(Deserialize)] struct Data { url: String, @@ -218,7 +224,7 @@ pub async fn desktop_video_progress( video_id: &str, uploaded: u64, total: u64, -) -> Result<(), String> { +) -> Result<(), AuthedApiError> { let resp = app .authed_api_request("/api/desktop/video/progress", |client, url| { client.post(url).json(&json!({ diff --git a/apps/desktop/src-tauri/src/auth.rs b/apps/desktop/src-tauri/src/auth.rs index 97f999a4b2..698c41f10b 100644 --- a/apps/desktop/src-tauri/src/auth.rs +++ b/apps/desktop/src-tauri/src/auth.rs @@ -118,6 +118,3 @@ impl AuthStore { store.save().map_err(|e| e.to_string()) } } - -#[derive(specta::Type, serde::Serialize, tauri_specta::Event, Debug, Clone, serde::Deserialize)] -pub struct AuthenticationInvalid; diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index b73a6fc5d5..3e394a052f 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -28,7 +28,7 @@ mod web_api; mod windows; use audio::AppSounds; -use auth::{AuthStore, AuthenticationInvalid, Plan}; +use auth::{AuthStore, Plan}; use camera::CameraPreviewState; use cap_editor::{EditorInstance, EditorState}; use cap_project::{ @@ -2000,7 +2000,6 @@ pub async fn run(recording_logging_handle: LoggingHandle) { RequestOpenSettings, RequestScreenCapturePrewarm, NewNotification, - AuthenticationInvalid, audio_meter::AudioInputLevelChange, captions::DownloadProgress, recording::RecordingEvent, diff --git a/apps/desktop/src-tauri/src/recording.rs b/apps/desktop/src-tauri/src/recording.rs index 8db9417449..50290f4a5c 100644 --- a/apps/desktop/src-tauri/src/recording.rs +++ b/apps/desktop/src-tauri/src/recording.rs @@ -37,6 +37,7 @@ use tauri_plugin_dialog::{DialogExt, MessageDialogBuilder}; use tauri_specta::Event; use tracing::*; +use crate::web_api::AuthedApiError; use crate::{ App, CurrentRecordingChanged, MutableState, NewStudioRecordingAdded, RecordingState, RecordingStopped, VideoUploadInfo, @@ -252,7 +253,7 @@ pub async fn start_recording( app: AppHandle, state_mtx: MutableState<'_, App>, inputs: StartRecordingInputs, -) -> Result<(), String> { +) -> Result<(), AuthedApiError> { if !matches!(state_mtx.read().await.recording_state, RecordingState::None) { return Err("Recording already in progress".to_string()); } diff --git a/apps/desktop/src-tauri/src/upload.rs b/apps/desktop/src-tauri/src/upload.rs index 7994c33f21..16246b490e 100644 --- a/apps/desktop/src-tauri/src/upload.rs +++ b/apps/desktop/src-tauri/src/upload.rs @@ -87,7 +87,6 @@ pub async fn upload_video( id: v.id, }); } ->>>>>>> main info!("Uploading video {video_id}..."); diff --git a/apps/desktop/src-tauri/src/web_api.rs b/apps/desktop/src-tauri/src/web_api.rs index c152d2e66a..085b2276b8 100644 --- a/apps/desktop/src-tauri/src/web_api.rs +++ b/apps/desktop/src-tauri/src/web_api.rs @@ -6,7 +6,7 @@ use tracing::{error, warn}; use crate::{ ArcLock, - auth::{AuthSecret, AuthStore, AuthenticationInvalid}, + auth::{AuthSecret, AuthStore}, }; #[derive(Error, Debug)] @@ -83,7 +83,6 @@ impl + Emitter, R: Runtime> ManagerExt for T { let Some(auth) = AuthStore::get(self.app_handle()).map_err(AuthedApiError::AuthStore)? else { warn!("Not logged in"); - AuthenticationInvalid.emit(self).ok(); return Err(AuthedApiError::InvalidAuthentication); }; @@ -92,7 +91,6 @@ impl + Emitter, R: Runtime> ManagerExt for T { if response.status() == StatusCode::UNAUTHORIZED { error!("Authentication expired. Please log in again."); - AuthenticationInvalid.emit(self).ok(); return Err(AuthedApiError::InvalidAuthentication); } diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx index 22c667683f..b06e408231 100644 --- a/apps/desktop/src/routes/(window-chrome)/(main).tsx +++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx @@ -238,11 +238,16 @@ function Page() { } })(); - await commands.startRecording({ - capture_target, - mode: payload.mode, - capture_system_audio: rawOptions.captureSystemAudio, - }); + try { + await commands.startRecording({ + capture_target, + mode: payload.mode, + capture_system_audio: rawOptions.captureSystemAudio, + }); + } catch (err) { + alert("CRINGE"); + throw err; + } } else await commands.stopRecording(); }, })); diff --git a/apps/desktop/src/routes/target-select-overlay.tsx b/apps/desktop/src/routes/target-select-overlay.tsx index 1ceafbb712..7c8a193c27 100644 --- a/apps/desktop/src/routes/target-select-overlay.tsx +++ b/apps/desktop/src/routes/target-select-overlay.tsx @@ -811,11 +811,17 @@ function RecordingControls(props: { return; } - commands.startRecording({ - capture_target: props.target, - mode: rawOptions.mode, - capture_system_audio: rawOptions.captureSystemAudio, - }); + commands + .startRecording({ + capture_target: props.target, + mode: rawOptions.mode, + capture_system_audio: rawOptions.captureSystemAudio, + }) + .catch((err) => { + console.log(err); + alert("CRINGE"); + throw err; + }); }} >
From 52fc85d52c16895606b3df7fe416427472d626fe Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 13 Oct 2025 14:49:57 +0800 Subject: [PATCH 4/7] fix error handling --- apps/desktop/src-tauri/src/api.rs | 24 ++++------- .../desktop/src-tauri/src/deeplink_actions.rs | 4 +- apps/desktop/src-tauri/src/hotkeys.rs | 6 +-- apps/desktop/src-tauri/src/lib.rs | 8 ++-- apps/desktop/src-tauri/src/recording.rs | 41 +++++++++++++----- apps/desktop/src-tauri/src/upload.rs | 26 +++++++----- apps/desktop/src-tauri/src/web_api.rs | 2 + .../src/routes/(window-chrome)/(main).tsx | 13 +++--- .../src/routes/in-progress-recording.tsx | 5 ++- .../src/routes/target-select-overlay.tsx | 15 ++++--- apps/desktop/src/utils/recording.ts | 42 +++++++++++++++++++ apps/desktop/src/utils/tauri.ts | 8 ++-- 12 files changed, 131 insertions(+), 63 deletions(-) create mode 100644 apps/desktop/src/utils/recording.ts diff --git a/apps/desktop/src-tauri/src/api.rs b/apps/desktop/src-tauri/src/api.rs index c8467ad304..fdfab9c4c2 100644 --- a/apps/desktop/src-tauri/src/api.rs +++ b/apps/desktop/src-tauri/src/api.rs @@ -35,14 +35,12 @@ pub async fn upload_multipart_initiate( .text() .await .unwrap_or_else(|_| "".to_string()); - return Err(format!( - "api/upload_multipart_initiate/{status}: {error_body}" - )); + return Err(format!("api/upload_multipart_initiate/{status}: {error_body}").into()); } resp.json::() .await - .map_err(|err| format!("api/upload_multipart_initiate/response: {err}")) + .map_err(|err| format!("api/upload_multipart_initiate/response: {err}").into()) .map(|data| data.upload_id) } @@ -79,14 +77,12 @@ pub async fn upload_multipart_presign_part( .text() .await .unwrap_or_else(|_| "".to_string()); - return Err(format!( - "api/upload_multipart_presign_part/{status}: {error_body}" - )); + return Err(format!("api/upload_multipart_presign_part/{status}: {error_body}").into()); } resp.json::() .await - .map_err(|err| format!("api/upload_multipart_presign_part/response: {err}")) + .map_err(|err| format!("api/upload_multipart_presign_part/response: {err}").into()) .map(|data| data.presigned_url) } @@ -153,14 +149,12 @@ pub async fn upload_multipart_complete( .text() .await .unwrap_or_else(|_| "".to_string()); - return Err(format!( - "api/upload_multipart_complete/{status}: {error_body}" - )); + return Err(format!("api/upload_multipart_complete/{status}: {error_body}").into()); } resp.json::() .await - .map_err(|err| format!("api/upload_multipart_complete/response: {err}")) + .map_err(|err| format!("api/upload_multipart_complete/response: {err}").into()) .map(|data| data.location) } @@ -210,12 +204,12 @@ pub async fn upload_signed( .text() .await .unwrap_or_else(|_| "".to_string()); - return Err(format!("api/upload_signed/{status}: {error_body}")); + return Err(format!("api/upload_signed/{status}: {error_body}").into()); } resp.json::() .await - .map_err(|err| format!("api/upload_signed/response: {err}")) + .map_err(|err| format!("api/upload_signed/response: {err}").into()) .map(|data| data.presigned_put_data.url) } @@ -243,7 +237,7 @@ pub async fn desktop_video_progress( .text() .await .unwrap_or_else(|_| "".to_string()); - return Err(format!("api/desktop_video_progress/{status}: {error_body}")); + return Err(format!("api/desktop_video_progress/{status}: {error_body}").into()); } Ok(()) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index aaf6a62b80..7372da2b9b 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -138,7 +138,9 @@ impl DeepLinkAction { mode, }; - crate::recording::start_recording(app.clone(), state, inputs).await + crate::recording::start_recording(app.clone(), state, inputs) + .await + .map(|_| ()) } DeepLinkAction::StopRecording => { crate::recording::stop_recording(app.clone(), app.state()).await diff --git a/apps/desktop/src-tauri/src/hotkeys.rs b/apps/desktop/src-tauri/src/hotkeys.rs index 4ba18473cb..79a4a5eff3 100644 --- a/apps/desktop/src-tauri/src/hotkeys.rs +++ b/apps/desktop/src-tauri/src/hotkeys.rs @@ -146,9 +146,9 @@ async fn handle_hotkey(app: AppHandle, action: HotkeyAction) -> Result<(), Strin Ok(()) } HotkeyAction::StopRecording => recording::stop_recording(app.clone(), app.state()).await, - HotkeyAction::RestartRecording => { - recording::restart_recording(app.clone(), app.state()).await - } + HotkeyAction::RestartRecording => recording::restart_recording(app.clone(), app.state()) + .await + .map(|_| ()), HotkeyAction::OpenRecordingPicker => { let _ = RequestOpenRecordingPicker { target_mode: None }.emit(&app); Ok(()) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 3e394a052f..77e6377fc8 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -86,7 +86,7 @@ use tauri_specta::Event; use tokio::sync::Mutex; use tokio::sync::{RwLock, oneshot}; use tracing::{error, trace, warn}; -use upload::{S3UploadMeta, create_or_get_video, upload_image, upload_video}; +use upload::{create_or_get_video, upload_image, upload_video}; use web_api::AuthedApiError; use web_api::ManagerExt as WebManagerExt; use windows::{CapWindowId, EditorWindowIds, ShowCapWindow, set_window_transparent}; @@ -1167,7 +1167,9 @@ async fn upload_exported_video( NotificationType::UploadFailed.send(&app); - meta.upload = Some(UploadMeta::Failed { error: e.to_string() }); + meta.upload = Some(UploadMeta::Failed { + error: e.to_string(), + }); meta.save_for_project() .map_err(|e| error!("Failed to save recording meta: {e}")) .ok(); @@ -2549,7 +2551,7 @@ async fn resume_uploads(app: AppHandle) -> Result<(), String> { error!("Error completing resumed upload for video: {error}"); if let Ok(mut meta) = RecordingMeta::load_for_project(&recording_dir).map_err(|err| error!("Error loading project metadata: {err}")) { - meta.upload = Some(UploadMeta::Failed { error }); + meta.upload = Some(UploadMeta::Failed { error: error.to_string() }); meta.save_for_project().map_err(|err| error!("Error saving project metadata: {err}")).ok(); } }) diff --git a/apps/desktop/src-tauri/src/recording.rs b/apps/desktop/src-tauri/src/recording.rs index 50290f4a5c..8e38a60dae 100644 --- a/apps/desktop/src-tauri/src/recording.rs +++ b/apps/desktop/src-tauri/src/recording.rs @@ -246,6 +246,13 @@ pub enum RecordingEvent { Failed { error: String }, } +#[derive(Serialize, Type)] +pub enum RecordingAction { + Started, + InvalidAuthentication, + UpgradeRequired, +} + #[tauri::command] #[specta::specta] #[tracing::instrument(name = "recording", skip_all)] @@ -253,7 +260,7 @@ pub async fn start_recording( app: AppHandle, state_mtx: MutableState<'_, App>, inputs: StartRecordingInputs, -) -> Result<(), AuthedApiError> { +) -> Result { if !matches!(state_mtx.read().await.recording_state, RecordingState::None) { return Err("Recording already in progress".to_string()); } @@ -295,7 +302,7 @@ pub async fn start_recording( match AuthStore::get(&app).ok().flatten() { Some(_) => { // Pre-create the video and get the shareable link - let s3_config = create_or_get_video( + let s3_config = match create_or_get_video( &app, false, None, @@ -306,10 +313,19 @@ pub async fn start_recording( None, ) .await - .map_err(|err| { - error!("Error creating instant mode video: {err}"); - err - })?; + { + Ok(meta) => meta, + Err(AuthedApiError::InvalidAuthentication) => { + return Ok(RecordingAction::InvalidAuthentication); + } + Err(AuthedApiError::UpgradeRequired) => { + return Ok(RecordingAction::UpgradeRequired); + } + Err(err) => { + error!("Error creating instant mode video: {err}"); + return Err(err.to_string()); + } + }; let link = app.make_app_url(format!("/s/{}", s3_config.id)).await; info!("Pre-created shareable link: {}", link); @@ -619,7 +635,7 @@ pub async fn start_recording( AppSounds::StartRecording.play(); - Ok(()) + Ok(RecordingAction::Started) } #[tauri::command] @@ -664,7 +680,10 @@ pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Res #[tauri::command] #[specta::specta] -pub async fn restart_recording(app: AppHandle, state: MutableState<'_, App>) -> Result<(), String> { +pub async fn restart_recording( + app: AppHandle, + state: MutableState<'_, App>, +) -> Result { let Some(recording) = state.write().await.clear_current_recording() else { return Err("No recording in progress".to_string()); }; @@ -878,7 +897,7 @@ async fn handle_recording_finish( .handle .await .map_err(|e| e.to_string()) - .and_then(|r| r) + .and_then(|r| r.map_err(|v| v.to_string())) { Ok(()) => { info!( @@ -936,7 +955,9 @@ async fn handle_recording_finish( error!("Error in upload_video: {error}"); if let Ok(mut meta) = RecordingMeta::load_for_project(&recording_dir) { - meta.upload = Some(UploadMeta::Failed { error }); + meta.upload = Some(UploadMeta::Failed { + error: error.to_string(), + }); meta.save_for_project() .map_err(|e| format!("Failed to save recording meta: {e}")) .ok(); diff --git a/apps/desktop/src-tauri/src/upload.rs b/apps/desktop/src-tauri/src/upload.rs index 16246b490e..2c2b0c6d24 100644 --- a/apps/desktop/src-tauri/src/upload.rs +++ b/apps/desktop/src-tauri/src/upload.rs @@ -5,7 +5,7 @@ use crate::{ api::{self, PresignedS3PutRequest, PresignedS3PutRequestMethod, S3VideoMeta, UploadedPart}, general_settings::GeneralSettingsStore, upload_legacy, - web_api::ManagerExt, + web_api::{AuthedApiError, ManagerExt}, }; use async_stream::{stream, try_stream}; use axum::http::Uri; @@ -17,6 +17,7 @@ use flume::Receiver; use futures::{Stream, StreamExt, TryStreamExt, stream}; use image::{ImageReader, codecs::jpeg::JpegEncoder}; use reqwest::StatusCode; +use sentry::types::Auth; use serde::{Deserialize, Serialize}; use specta::Type; use std::{ @@ -129,7 +130,7 @@ pub async fn upload_video( stream::once(async move { Ok::<_, std::io::Error>(bytes::Bytes::from(bytes)) }), ); - let (video_result, thumbnail_result): (Result<_, String>, Result<_, String>) = + let (video_result, thumbnail_result): (Result<_, AuthedApiError>, Result<_, AuthedApiError>) = tokio::join!(video_fut, thumbnail_fut); let _ = (video_result?, thumbnail_result?); @@ -154,7 +155,10 @@ async fn file_reader_stream(path: impl AsRef) -> Result<(ReaderStream Result { +pub async fn upload_image( + app: &AppHandle, + file_path: PathBuf, +) -> Result { let is_new_uploader_enabled = GeneralSettingsStore::get(app) .map_err(|err| error!("Error checking status of new uploader flow from settings: {err}")) .ok() @@ -167,7 +171,8 @@ pub async fn upload_image(app: &AppHandle, file_path: PathBuf) -> Result, meta: Option, ) -> Result { - return Err(AuthedApiError::InvalidAuthentication); // TODO + return Err(AuthedApiError::Other("A made up error".into())); // TODO let mut s3_config_url = if let Some(id) = video_id { format!("/api/desktop/video/create?recordingMode=desktopMP4&videoId={id}") @@ -315,7 +320,7 @@ pub async fn compress_image(path: PathBuf) -> Result, String> { } pub struct InstantMultipartUpload { - pub handle: tokio::task::JoinHandle>, + pub handle: tokio::task::JoinHandle>, } impl InstantMultipartUpload { @@ -345,7 +350,7 @@ impl InstantMultipartUpload { pre_created_video: VideoUploadInfo, realtime_video_done: Option>, recording_dir: PathBuf, - ) -> Result<(), String> { + ) -> Result<(), AuthedApiError> { let is_new_uploader_enabled = GeneralSettingsStore::get(&app) .map_err(|err| { error!("Error checking status of new uploader flow from settings: {err}") @@ -362,7 +367,8 @@ impl InstantMultipartUpload { pre_created_video, realtime_video_done, ) - .await; + .await + .map_err(Into::into); } let video_id = pre_created_video.id.clone(); @@ -594,7 +600,7 @@ fn multipart_uploader( video_id: String, upload_id: String, stream: impl Stream>, -) -> impl Stream> { +) -> impl Stream> { debug!("Initializing multipart uploader for video {video_id:?}"); try_stream! { @@ -647,7 +653,7 @@ pub async fn singlepart_uploader( request: PresignedS3PutRequest, total_size: u64, stream: impl Stream> + Send + 'static, -) -> Result<(), String> { +) -> Result<(), AuthedApiError> { let presigned_url = api::upload_signed(&app, request).await?; let url = Uri::from_str(&presigned_url) diff --git a/apps/desktop/src-tauri/src/web_api.rs b/apps/desktop/src-tauri/src/web_api.rs index 085b2276b8..5f18c35ea3 100644 --- a/apps/desktop/src-tauri/src/web_api.rs +++ b/apps/desktop/src-tauri/src/web_api.rs @@ -1,4 +1,6 @@ use reqwest::StatusCode; +use serde::Serialize; +use specta::Type; use tauri::{Emitter, Manager, Runtime}; use tauri_specta::Event; use thiserror::Error; diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx index b06e408231..de8de66f8f 100644 --- a/apps/desktop/src/routes/(window-chrome)/(main).tsx +++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx @@ -238,16 +238,14 @@ function Page() { } })(); - try { - await commands.startRecording({ + await handleRecordingResult( + commands.startRecording({ capture_target, mode: payload.mode, capture_system_audio: rawOptions.captureSystemAudio, - }); - } catch (err) { - alert("CRINGE"); - throw err; - } + }), + setOptions, + ); } else await commands.stopRecording(); }, })); @@ -600,6 +598,7 @@ import { RecordingOptionsProvider, useRecordingOptions, } from "./OptionsContext"; +import { handleRecordingResult } from "~/utils/recording"; let hasChecked = false; function createUpdateCheck() { diff --git a/apps/desktop/src/routes/in-progress-recording.tsx b/apps/desktop/src/routes/in-progress-recording.tsx index 17682f5da5..4b2f146d9a 100644 --- a/apps/desktop/src/routes/in-progress-recording.tsx +++ b/apps/desktop/src/routes/in-progress-recording.tsx @@ -19,7 +19,9 @@ import { createCurrentRecordingQuery, createOptionsQuery, } from "~/utils/queries"; +import { handleRecordingResult } from "~/utils/recording"; import { commands, events } from "~/utils/tauri"; +import { useRecordingOptions } from "./(window-chrome)/OptionsContext"; type State = | { variant: "countdown"; from: number; current: number } @@ -48,6 +50,7 @@ export default function () { const [start, setStart] = createSignal(Date.now()); const [time, setTime] = createSignal(Date.now()); const currentRecording = createCurrentRecordingQuery(); + const { setOptions } = useRecordingOptions(); const optionsQuery = createOptionsQuery(); const auth = authStore.createQuery(); @@ -128,7 +131,7 @@ export default function () { if (!shouldRestart) return; - await commands.restartRecording(); + await handleRecordingResult(commands.restartRecording(), setOptions); setState({ variant: "recording" }); setTime(Date.now()); diff --git a/apps/desktop/src/routes/target-select-overlay.tsx b/apps/desktop/src/routes/target-select-overlay.tsx index 7c8a193c27..59155c59e1 100644 --- a/apps/desktop/src/routes/target-select-overlay.tsx +++ b/apps/desktop/src/routes/target-select-overlay.tsx @@ -7,6 +7,7 @@ import { useSearchParams } from "@solidjs/router"; import { createQuery } from "@tanstack/solid-query"; import { emit } from "@tauri-apps/api/event"; import { CheckMenuItem, Menu, Submenu } from "@tauri-apps/api/menu"; +import * as dialog from "@tauri-apps/plugin-dialog"; import { cx } from "cva"; import { type ComponentProps, @@ -35,6 +36,7 @@ import { RecordingOptionsProvider, useRecordingOptions, } from "./(window-chrome)/OptionsContext"; +import { handleRecordingResult } from "~/utils/recording"; const capitalize = (str: string) => { return str.charAt(0).toUpperCase() + str.slice(1); @@ -811,17 +813,14 @@ function RecordingControls(props: { return; } - commands - .startRecording({ + handleRecordingResult( + commands.startRecording({ capture_target: props.target, mode: rawOptions.mode, capture_system_audio: rawOptions.captureSystemAudio, - }) - .catch((err) => { - console.log(err); - alert("CRINGE"); - throw err; - }); + }), + setOptions, + ); }} >
diff --git a/apps/desktop/src/utils/recording.ts b/apps/desktop/src/utils/recording.ts new file mode 100644 index 0000000000..38ceaf8252 --- /dev/null +++ b/apps/desktop/src/utils/recording.ts @@ -0,0 +1,42 @@ +import { emit } from "@tauri-apps/api/event"; +import * as dialog from "@tauri-apps/plugin-dialog"; +import { commands, RecordingAction, type StartRecordingInputs } from "./tauri"; +import type { SetStoreFunction } from "solid-js/store"; +import { createOptionsQuery } from "./queries"; + +const buttons = { + yes: "Login", + no: "Switch to Studio mode", + cancel: "Cancel", +}; + +export function handleRecordingResult( + result: Promise, + setOptions: ReturnType["setOptions"], +) { + return result + .then(async (result) => { + if (result === "InvalidAuthentication") { + const result = await dialog.message( + "You must be authenticated to start an instant mode recording. Login or switch to Studio mode.", + { + title: "Authentication required", + buttons, + }, + ); + + if (result === buttons.yes) emit("start-sign-in"); + else if (result === buttons.no) setOptions({ mode: "studio" }); + } else if (result === "UpgradeRequired") commands.showWindow("Upgrade"); + else + await dialog.message(`Error: ${result}`, { + title: "Error starting recording", + }); + }) + .catch((err) => + dialog.message(err, { + title: "Error starting recording", + kind: "error", + }), + ); +} diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index cfece4f3a0..0ae980b3ff 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -11,7 +11,7 @@ async setMicInput(label: string | null) : Promise { async setCameraInput(id: DeviceOrModelID | null) : Promise { return await TAURI_INVOKE("set_camera_input", { id }); }, -async startRecording(inputs: StartRecordingInputs) : Promise { +async startRecording(inputs: StartRecordingInputs) : Promise { return await TAURI_INVOKE("start_recording", { inputs }); }, async stopRecording() : Promise { @@ -23,7 +23,7 @@ async pauseRecording() : Promise { async resumeRecording() : Promise { return await TAURI_INVOKE("resume_recording"); }, -async restartRecording() : Promise { +async restartRecording() : Promise { return await TAURI_INVOKE("restart_recording"); }, async deleteRecording() : Promise { @@ -282,7 +282,6 @@ async editorDeleteProject() : Promise { export const events = __makeEvents__<{ audioInputLevelChange: AudioInputLevelChange, -authenticationInvalid: AuthenticationInvalid, currentRecordingChanged: CurrentRecordingChanged, downloadProgress: DownloadProgress, editorStateChanged: EditorStateChanged, @@ -305,7 +304,6 @@ targetUnderCursor: TargetUnderCursor, uploadProgressEvent: UploadProgressEvent }>({ audioInputLevelChange: "audio-input-level-change", -authenticationInvalid: "authentication-invalid", currentRecordingChanged: "current-recording-changed", downloadProgress: "download-progress", editorStateChanged: "editor-state-changed", @@ -346,7 +344,6 @@ export type AudioMeta = { path: string; start_time?: number | null } export type AuthSecret = { api_key: string } | { token: string; expires: number } export type AuthStore = { secret: AuthSecret; user_id: string | null; plan: Plan | null; intercom_hash: string | null } -export type AuthenticationInvalid = null export type BackgroundConfiguration = { source: BackgroundSource; blur: number; padding: number; rounding: number; inset: number; crop: Crop | null; shadow?: number; advancedShadow?: ShadowConfiguration | null; border?: BorderConfiguration | null } export type BackgroundSource = { type: "wallpaper"; path: string | null } | { type: "image"; path: string | null } | { type: "color"; value: [number, number, number] } | { type: "gradient"; from: [number, number, number]; to: [number, number, number]; angle?: number } export type BorderConfiguration = { enabled: boolean; width: number; color: [number, number, number]; opacity: number } @@ -433,6 +430,7 @@ export type Preset = { name: string; config: ProjectConfiguration } export type PresetsStore = { presets: Preset[]; default: number | null } export type ProjectConfiguration = { aspectRatio: AspectRatio | null; background: BackgroundConfiguration; camera: Camera; audio: AudioConfiguration; cursor: CursorConfiguration; hotkeys: HotkeysConfiguration; timeline?: TimelineConfiguration | null; captions?: CaptionsData | null; clips?: ClipConfiguration[] } export type ProjectRecordingsMeta = { segments: SegmentRecordings[] } +export type RecordingAction = "Started" | "InvalidAuthentication" | "UpgradeRequired" export type RecordingDeleted = { path: string } export type RecordingEvent = { variant: "Countdown"; value: number } | { variant: "Started" } | { variant: "Stopped" } | { variant: "Failed"; error: string } export type RecordingMeta = (StudioRecordingMeta | InstantRecordingMeta) & { platform?: Platform | null; pretty_name: string; sharing?: SharingMeta | null; upload?: UploadMeta | null } From ce5d707e2186f73f98c52a740018e99dd8ca8a70 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 13 Oct 2025 14:50:48 +0800 Subject: [PATCH 5/7] format --- apps/desktop/src/routes/(window-chrome)/(main).tsx | 2 +- apps/desktop/src/routes/target-select-overlay.tsx | 2 +- apps/desktop/src/utils/recording.ts | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/routes/(window-chrome)/(main).tsx b/apps/desktop/src/routes/(window-chrome)/(main).tsx index de8de66f8f..5a98956cc4 100644 --- a/apps/desktop/src/routes/(window-chrome)/(main).tsx +++ b/apps/desktop/src/routes/(window-chrome)/(main).tsx @@ -592,13 +592,13 @@ import { Transition } from "solid-transition-group"; import { SignInButton } from "~/components/SignInButton"; import { authStore, generalSettingsStore } from "~/store"; import { createTauriEventListener } from "~/utils/createEventListener"; +import { handleRecordingResult } from "~/utils/recording"; import { apiClient } from "~/utils/web-api"; import { WindowChromeHeader } from "./Context"; import { RecordingOptionsProvider, useRecordingOptions, } from "./OptionsContext"; -import { handleRecordingResult } from "~/utils/recording"; let hasChecked = false; function createUpdateCheck() { diff --git a/apps/desktop/src/routes/target-select-overlay.tsx b/apps/desktop/src/routes/target-select-overlay.tsx index 59155c59e1..e0c44bf295 100644 --- a/apps/desktop/src/routes/target-select-overlay.tsx +++ b/apps/desktop/src/routes/target-select-overlay.tsx @@ -25,6 +25,7 @@ import { createStore, reconcile } from "solid-js/store"; import ModeSelect from "~/components/ModeSelect"; import { authStore, generalSettingsStore } from "~/store"; import { createOptionsQuery } from "~/utils/queries"; +import { handleRecordingResult } from "~/utils/recording"; import { commands, type DisplayId, @@ -36,7 +37,6 @@ import { RecordingOptionsProvider, useRecordingOptions, } from "./(window-chrome)/OptionsContext"; -import { handleRecordingResult } from "~/utils/recording"; const capitalize = (str: string) => { return str.charAt(0).toUpperCase() + str.slice(1); diff --git a/apps/desktop/src/utils/recording.ts b/apps/desktop/src/utils/recording.ts index 38ceaf8252..1a3ddeec39 100644 --- a/apps/desktop/src/utils/recording.ts +++ b/apps/desktop/src/utils/recording.ts @@ -1,8 +1,12 @@ import { emit } from "@tauri-apps/api/event"; import * as dialog from "@tauri-apps/plugin-dialog"; -import { commands, RecordingAction, type StartRecordingInputs } from "./tauri"; import type { SetStoreFunction } from "solid-js/store"; -import { createOptionsQuery } from "./queries"; +import type { createOptionsQuery } from "./queries"; +import { + commands, + type RecordingAction, + type StartRecordingInputs, +} from "./tauri"; const buttons = { yes: "Login", From 41102e26303d387843fac9168fe0763bfa5852c0 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 13 Oct 2025 14:56:15 +0800 Subject: [PATCH 6/7] fix --- .../src/routes/in-progress-recording.tsx | 4 +-- apps/desktop/src/utils/recording.ts | 32 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/desktop/src/routes/in-progress-recording.tsx b/apps/desktop/src/routes/in-progress-recording.tsx index 4b2f146d9a..9459c4136d 100644 --- a/apps/desktop/src/routes/in-progress-recording.tsx +++ b/apps/desktop/src/routes/in-progress-recording.tsx @@ -21,7 +21,6 @@ import { } from "~/utils/queries"; import { handleRecordingResult } from "~/utils/recording"; import { commands, events } from "~/utils/tauri"; -import { useRecordingOptions } from "./(window-chrome)/OptionsContext"; type State = | { variant: "countdown"; from: number; current: number } @@ -50,7 +49,6 @@ export default function () { const [start, setStart] = createSignal(Date.now()); const [time, setTime] = createSignal(Date.now()); const currentRecording = createCurrentRecordingQuery(); - const { setOptions } = useRecordingOptions(); const optionsQuery = createOptionsQuery(); const auth = authStore.createQuery(); @@ -131,7 +129,7 @@ export default function () { if (!shouldRestart) return; - await handleRecordingResult(commands.restartRecording(), setOptions); + await handleRecordingResult(commands.restartRecording(), undefined); setState({ variant: "recording" }); setTime(Date.now()); diff --git a/apps/desktop/src/utils/recording.ts b/apps/desktop/src/utils/recording.ts index 1a3ddeec39..bd9011633e 100644 --- a/apps/desktop/src/utils/recording.ts +++ b/apps/desktop/src/utils/recording.ts @@ -1,26 +1,26 @@ import { emit } from "@tauri-apps/api/event"; import * as dialog from "@tauri-apps/plugin-dialog"; -import type { SetStoreFunction } from "solid-js/store"; import type { createOptionsQuery } from "./queries"; -import { - commands, - type RecordingAction, - type StartRecordingInputs, -} from "./tauri"; - -const buttons = { - yes: "Login", - no: "Switch to Studio mode", - cancel: "Cancel", -}; +import { commands, type RecordingAction } from "./tauri"; export function handleRecordingResult( result: Promise, - setOptions: ReturnType["setOptions"], + setOptions: ReturnType["setOptions"] | undefined, ) { return result .then(async (result) => { if (result === "InvalidAuthentication") { + const buttons = setOptions + ? { + yes: "Login", + no: "Switch to Studio mode", + cancel: "Cancel", + } + : { + ok: "Login", + cancel: "Cancel", + }; + const result = await dialog.message( "You must be authenticated to start an instant mode recording. Login or switch to Studio mode.", { @@ -29,8 +29,10 @@ export function handleRecordingResult( }, ); - if (result === buttons.yes) emit("start-sign-in"); - else if (result === buttons.no) setOptions({ mode: "studio" }); + if (result === buttons.yes || result === buttons.ok) + emit("start-sign-in"); + else if (result === buttons.no && setOptions) + setOptions({ mode: "studio" }); } else if (result === "UpgradeRequired") commands.showWindow("Upgrade"); else await dialog.message(`Error: ${result}`, { From e933050ab6659493d72a8e98951cf2462e784e35 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Mon, 13 Oct 2025 14:58:03 +0800 Subject: [PATCH 7/7] fix --- apps/desktop/src-tauri/src/upload.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/desktop/src-tauri/src/upload.rs b/apps/desktop/src-tauri/src/upload.rs index 2c2b0c6d24..4956f35a36 100644 --- a/apps/desktop/src-tauri/src/upload.rs +++ b/apps/desktop/src-tauri/src/upload.rs @@ -210,8 +210,6 @@ pub async fn create_or_get_video( name: Option, meta: Option, ) -> Result { - return Err(AuthedApiError::Other("A made up error".into())); // TODO - let mut s3_config_url = if let Some(id) = video_id { format!("/api/desktop/video/create?recordingMode=desktopMP4&videoId={id}") } else if is_screenshot {