Skip to content
35 changes: 35 additions & 0 deletions apps/desktop/src-tauri/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ pub struct AuthStore {
pub user_id: Option<String>,
pub plan: Option<Plan>,
pub intercom_hash: Option<String>,
#[serde(default)]
pub organizations: Vec<Organization>,
}

#[derive(Serialize, Deserialize, Type, Debug, Clone)]
pub struct Organization {
pub id: String,
pub name: String,
#[serde(rename = "ownerId")]
pub owner_id: String,
}

#[derive(Serialize, Deserialize, Type, Debug)]
Expand Down Expand Up @@ -97,6 +107,31 @@ impl AuthStore {
});
auth.intercom_hash = Some(plan_response.intercom_hash.unwrap_or_default());

// Fetch organizations
println!("Fetching organizations for user");
match app
.authed_api_request("/api/desktop/organizations", |client, url| client.get(url))
.await
{
Ok(response) if response.status().is_success() => {
match response.json::<Vec<Organization>>().await {
Ok(orgs) => {
println!("Fetched {} organizations", orgs.len());
auth.organizations = orgs;
}
Err(e) => {
println!("Failed to parse organizations: {e}");
}
}
}
Err(e) => {
println!("Failed to fetch organizations: {e}");
}
Ok(response) => {
println!("Failed to fetch organizations: status {}", response.status());
}
}

Self::set(app, Some(auth))?;

Ok(())
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ impl DeepLinkAction {
capture_target,
capture_system_audio,
mode,
organization_id: None,
};

crate::recording::start_recording(app.clone(), state, inputs)
Expand Down
43 changes: 40 additions & 3 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ use tauri_specta::Event;
#[cfg(target_os = "macos")]
use tokio::sync::Mutex;
use tokio::sync::{RwLock, oneshot};
use tracing::{error, trace, warn};
use tracing::{error, info, trace, warn};
use upload::{create_or_get_video, upload_image, upload_video};
use web_api::AuthedApiError;
use web_api::ManagerExt as WebManagerExt;
Expand Down Expand Up @@ -1063,6 +1063,7 @@ async fn upload_exported_video(
path: PathBuf,
mode: UploadMode,
channel: Channel<UploadProgress>,
organization_id: Option<String>,
) -> Result<UploadResult, String> {
let Ok(Some(auth)) = AuthStore::get(&app) else {
AuthStore::set(&app, None).map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -1109,6 +1110,7 @@ async fn upload_exported_video(
video_id,
Some(meta.pretty_name.clone()),
Some(metadata.clone()),
organization_id,
)
.await
}
Expand Down Expand Up @@ -1631,6 +1633,7 @@ async fn check_upgraded_and_update(app: AppHandle) -> Result<bool, String> {
manual: auth.plan.map(|p| p.manual).unwrap_or(false),
last_checked: chrono::Utc::now().timestamp() as i32,
}),
organizations: auth.organizations,
};
println!("Updating auth store with new pro status");
AuthStore::set(&app, Some(updated_auth)).map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -1886,6 +1889,13 @@ async fn update_auth_plan(app: AppHandle) {
AuthStore::update_auth_plan(&app).await.ok();
}

#[tauri::command]
#[specta::specta]
async fn refresh_organizations(app: AppHandle) -> Result<(), String> {
info!("Manually refreshing organizations");
AuthStore::update_auth_plan(&app).await
}

pub type FilteredRegistry = tracing_subscriber::layer::Layered<
tracing_subscriber::filter::FilterFn<fn(m: &tracing::Metadata) -> bool>,
tracing_subscriber::Registry,
Expand Down Expand Up @@ -1962,12 +1972,14 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
windows::position_traffic_lights,
windows::set_theme,
global_message_dialog,
log_message,
show_window,
write_clipboard_string,
platform::perform_haptic_feedback,
list_fails,
set_fail,
update_auth_plan,
refresh_organizations,
set_window_transparent,
get_editor_meta,
set_pretty_name,
Expand Down Expand Up @@ -2062,8 +2074,8 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
tauri::async_runtime::set(tokio::runtime::Handle::current());

#[allow(unused_mut)]
let mut builder =
tauri::Builder::default().plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
let mut builder = tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
trace!("Single instance invoked with args {args:?}");

// This is also handled as a deeplink on some platforms (eg macOS), see deeplink_actions
Expand Down Expand Up @@ -2172,6 +2184,19 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
..Default::default()
}));
});

// Fetch organizations if missing (for existing users)
if auth.organizations.is_empty() {
info!("User is logged in but organizations not cached, fetching...");
let app_clone = app.clone();
tokio::spawn(async move {
if let Err(e) = AuthStore::update_auth_plan(&app_clone).await {
error!("Failed to fetch organizations on startup: {}", e);
} else {
info!("Organizations fetched successfully on startup");
}
});
}
}

tokio::spawn({
Expand Down Expand Up @@ -2274,6 +2299,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
}),
capture_system_audio: settings.system_audio,
mode: event.mode,
organization_id: settings.organization_id,
},
)
.await;
Expand Down Expand Up @@ -2651,6 +2677,17 @@ fn global_message_dialog(app: AppHandle, message: String) {
app.dialog().message(message).show(|_| {});
}

#[tauri::command]
#[specta::specta]
fn log_message(level: String, message: String) {
match level.as_str() {
"info" => info!("{}", message),
"warn" => warn!("{}", message),
"error" => error!("{}", message),
_ => trace!("{}", message),
}
}

#[tauri::command]
#[specta::specta]
async fn write_clipboard_string(
Expand Down
3 changes: 3 additions & 0 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ pub struct StartRecordingInputs {
#[serde(default)]
pub capture_system_audio: bool,
pub mode: RecordingMode,
#[serde(default)]
pub organization_id: Option<String>,
}

#[derive(tauri_specta::Event, specta::Type, Clone, Debug, serde::Serialize)]
Expand Down Expand Up @@ -313,6 +315,7 @@ pub async fn start_recording(
chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
)),
None,
inputs.organization_id.clone(),
)
.await
{
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/src/recording_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub struct RecordingSettingsStore {
pub camera_id: Option<DeviceOrModelID>,
pub mode: Option<RecordingMode>,
pub system_audio: bool,
pub organization_id: Option<String>,
}

impl RecordingSettingsStore {
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src-tauri/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub async fn upload_image(
.ok_or("Invalid file path")?
.to_string();

let s3_config = create_or_get_video(app, true, None, None, None).await?;
let s3_config = create_or_get_video(app, true, None, None, None, None).await?;

let (stream, total_size) = file_reader_stream(file_path).await?;
singlepart_uploader(
Expand Down Expand Up @@ -223,6 +223,7 @@ pub async fn create_or_get_video(
video_id: Option<String>,
name: Option<String>,
meta: Option<S3VideoMeta>,
organization_id: Option<String>,
) -> Result<S3UploadMeta, AuthedApiError> {
let mut s3_config_url = if let Some(id) = video_id {
format!("/api/desktop/video/create?recordingMode=desktopMP4&videoId={id}")
Expand All @@ -245,6 +246,10 @@ pub async fn create_or_get_video(
}
}

if let Some(org_id) = organization_id {
s3_config_url.push_str(&format!("&orgId={}", org_id));
}

let response = app
.authed_api_request(s3_config_url, |client, url| client.get(url))
.await?;
Expand Down
9 changes: 7 additions & 2 deletions apps/desktop/src-tauri/src/upload_legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ pub async fn upload_video(
let client = reqwest::Client::new();
let s3_config = match existing_config {
Some(config) => config,
None => create_or_get_video(app, false, Some(video_id.clone()), None, meta).await?,
None => create_or_get_video(app, false, Some(video_id.clone()), None, meta, None).await?,
};

let presigned_put = presigned_s3_put(
Expand Down Expand Up @@ -331,7 +331,7 @@ pub async fn upload_image(app: &AppHandle, file_path: PathBuf) -> Result<Uploade
.to_string();

let client = reqwest::Client::new();
let s3_config = create_or_get_video(app, true, None, None, None).await?;
let s3_config = create_or_get_video(app, true, None, None, None, None).await?;

let presigned_put = presigned_s3_put(
app,
Expand Down Expand Up @@ -385,6 +385,7 @@ pub async fn create_or_get_video(
video_id: Option<String>,
name: Option<String>,
meta: Option<S3VideoMeta>,
organization_id: Option<String>,
) -> Result<S3UploadMeta, String> {
let mut s3_config_url = if let Some(id) = video_id {
format!("/api/desktop/video/create?recordingMode=desktopMP4&videoId={id}")
Expand All @@ -407,6 +408,10 @@ pub async fn create_or_get_video(
}
}

if let Some(org_id) = organization_id {
s3_config_url.push_str(&format!("&orgId={}", org_id));
}

let response = app
.authed_api_request(s3_config_url, |client, url| client.get(url))
.await
Expand Down
5 changes: 5 additions & 0 deletions apps/desktop/src/routes/(window-chrome)/new-main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ function Page() {
currentWindow.setSize(new LogicalSize(size.width, size.height));
});

// Refresh organizations when main window loads
try {
await commands.refreshOrganizations();
} catch {}

onCleanup(async () => {
(await unlistenFocus)?.();
(await unlistenResize)?.();
Expand Down
Loading
Loading