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
2 changes: 1 addition & 1 deletion src-tauri/gen/schemas/acl-manifests.json

Large diffs are not rendered by default.

54 changes: 0 additions & 54 deletions src-tauri/gen/schemas/desktop-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2977,60 +2977,6 @@
"type": "string",
"const": "store:deny-values",
"markdownDescription": "Denies the values command without any pre-configured scope."
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string",
"const": "updater:default",
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-check",
"markdownDescription": "Enables the check command without any pre-configured scope."
},
{
"description": "Enables the download command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download",
"markdownDescription": "Enables the download command without any pre-configured scope."
},
{
"description": "Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download-and-install",
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
},
{
"description": "Enables the install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-install",
"markdownDescription": "Enables the install command without any pre-configured scope."
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-check",
"markdownDescription": "Denies the check command without any pre-configured scope."
},
{
"description": "Denies the download command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download",
"markdownDescription": "Denies the download command without any pre-configured scope."
},
{
"description": "Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download-and-install",
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
},
{
"description": "Denies the install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-install",
"markdownDescription": "Denies the install command without any pre-configured scope."
}
]
},
Expand Down
54 changes: 0 additions & 54 deletions src-tauri/gen/schemas/linux-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2977,60 +2977,6 @@
"type": "string",
"const": "store:deny-values",
"markdownDescription": "Denies the values command without any pre-configured scope."
},
{
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
"type": "string",
"const": "updater:default",
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
},
{
"description": "Enables the check command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-check",
"markdownDescription": "Enables the check command without any pre-configured scope."
},
{
"description": "Enables the download command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download",
"markdownDescription": "Enables the download command without any pre-configured scope."
},
{
"description": "Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-download-and-install",
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
},
{
"description": "Enables the install command without any pre-configured scope.",
"type": "string",
"const": "updater:allow-install",
"markdownDescription": "Enables the install command without any pre-configured scope."
},
{
"description": "Denies the check command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-check",
"markdownDescription": "Denies the check command without any pre-configured scope."
},
{
"description": "Denies the download command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download",
"markdownDescription": "Denies the download command without any pre-configured scope."
},
{
"description": "Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-download-and-install",
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
},
{
"description": "Denies the install command without any pre-configured scope.",
"type": "string",
"const": "updater:deny-install",
"markdownDescription": "Denies the install command without any pre-configured scope."
}
]
},
Expand Down
29 changes: 29 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ use crate::updater::{check_update, install_update};
use crate::accounts::{list_accounts, add_account, remove_account};
use crate::media::grant_media_permission;
use crate::drag_drop::handle_file_drop;
use crate::platform_manager::{PlatformManager, select_platform, get_current_platform, get_last_platform, list_platforms};
use crate::privacy_engine::{PrivacyEngine, clear_platform_session, clear_all_sessions, get_csp_for_platform};

mod accounts;
mod drag_drop;
mod media;
mod notifications;
mod platform;
mod platform_manager;
mod privacy;
mod privacy_engine;
mod shortcuts;
mod spellcheck;
mod theme_manager;
Expand Down Expand Up @@ -82,6 +86,10 @@ pub fn run() {
// Initialize shortcut manager
let shortcut_manager = crate::shortcuts::ShortcutManager::new();

// Initialize platform manager and privacy engine
let platform_manager = PlatformManager::new(&app_data_dir);
let privacy_engine = PrivacyEngine::new(app_data_dir.clone());

app.manage(notif_service);
app.manage(privacy_manager);
app.manage(theme_manager);
Expand All @@ -90,12 +98,22 @@ pub fn run() {
app.manage(tray);
app.manage(window_manager);
app.manage(std::sync::Mutex::new(shortcut_manager));
app.manage(platform_manager);
app.manage(privacy_engine);

// Initialize platform-specific features
platform::init(&handle);

Ok(())
})
.on_window_event(|window, event| {
if let tauri::WindowEvent::CloseRequested { .. } = event {
let engine = window.app_handle().state::<crate::privacy_engine::PrivacyEngine>();
if let Err(e) = engine.clear_all_sessions() {
log::warn!("[on_quit] failed to clear sessions: {}", e);
}
}
})
.invoke_handler(tauri::generate_handler![
// Notifications
show_notification,
Expand Down Expand Up @@ -175,6 +193,17 @@ pub fn run() {

// Drag & Drop
handle_file_drop,

// Platform
select_platform,
get_current_platform,
get_last_platform,
list_platforms,

// Privacy Engine
clear_platform_session,
clear_all_sessions,
get_csp_for_platform,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
Expand Down
180 changes: 180 additions & 0 deletions src-tauri/src/platform_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//! Platform manager for multi-platform messenger support
//!
//! This module manages platform selection, navigation, and state persistence.

use serde::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};
use tauri::Url;

/// Represents the supported social media platforms
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Platform {
/// Instagram Direct Messenger
Instagram,
/// Facebook Messenger
Messenger,
/// Facebook Messages
Facebook,
/// X (Twitter) Messages
X,
}

impl Platform {
/// Returns the URL for the platform's inbox/direct page
pub fn url(&self) -> &'static str {
match self {
Platform::Instagram => "https://www.instagram.com/direct/inbox/",
Platform::Messenger => "https://www.messenger.com",
Platform::Facebook => "https://www.facebook.com/messages/",
Platform::X => "https://x.com/messages",
}
}

/// Returns the display name of the platform
pub fn name(&self) -> &'static str {
match self {
Platform::Instagram => "Instagram",
Platform::Messenger => "Messenger",
Platform::Facebook => "Facebook",
Platform::X => "X",
}
}

/// Parses a platform name string into a Platform enum
pub fn from_str(s: &str) -> Option<Platform> {
match s {
"Instagram" => Some(Platform::Instagram),
"Messenger" => Some(Platform::Messenger),
"Facebook" => Some(Platform::Facebook),
"X" => Some(Platform::X),
_ => None,
}
}
}

/// Manages platform state and persistence
pub struct PlatformManager {
current: std::sync::Mutex<Option<Platform>>,
store_path: PathBuf,
}

impl PlatformManager {
/// Creates a new PlatformManager with the given app data directory
pub fn new(app_data_dir: &Path) -> Self {
let store_path = app_data_dir.join("platform.json");
let manager = Self {
current: std::sync::Mutex::new(None),
store_path,
};
manager.load_last();
manager
}

/// Gets the currently selected platform
pub fn get_current(&self) -> Option<Platform> {
self.current.lock().unwrap().clone()
}

/// Sets the current platform and persists it to disk
pub fn set_current(&self, platform: Platform) {
*self.current.lock().unwrap() = Some(platform);
self.persist();
}

/// Loads the last used platform from disk
pub fn load_last(&self) -> Option<Platform> {
if self.store_path.exists() {
let content = fs::read_to_string(&self.store_path).ok()?;
let platform = serde_json::from_str::<String>(&content).ok()?;
Platform::from_str(&platform).map(|p| {
*self.current.lock().unwrap() = Some(p.clone());
p
})
} else {
None
}
}

/// Persists the current platform to disk
fn persist(&self) {
if let Some(platform) = self.current.lock().unwrap().as_ref() {
let _ = fs::write(
&self.store_path,
serde_json::to_string(platform.name()).unwrap(),
);
}
}
}

/// Tauri command to select a platform by name
#[tauri::command]
pub fn select_platform(
platform_name: String,
manager: tauri::State<'_, PlatformManager>,
window: tauri::WebviewWindow,
) -> Result<String, String> {
let platform = Platform::from_str(&platform_name)
.ok_or_else(|| format!("Unknown platform: {}", platform_name))?;

manager.set_current(platform.clone());
let url = Url::parse(platform.url())
.map_err(|e| format!("Invalid platform URL: {}", e))?;
window
.navigate(url)
.map_err(|e| format!("Failed to navigate: {}", e))?;

Ok(format!("Selected platform: {}", platform.name()))
}

/// Tauri command to get the currently selected platform
#[tauri::command]
pub fn get_current_platform(manager: tauri::State<'_, PlatformManager>) -> Option<String> {
manager.get_current().map(|p| p.name().to_string())
}

/// Tauri command to get the last used platform from storage
#[tauri::command]
pub fn get_last_platform(manager: tauri::State<'_, PlatformManager>) -> Option<String> {
manager.load_last().map(|p| p.name().to_string())
}

/// Tauri command to list all available platforms
#[tauri::command]
pub fn list_platforms() -> Vec<serde_json::Value> {
vec![
serde_json::json!({"name": "Instagram", "url": Platform::Instagram.url()}),
serde_json::json!({"name": "Messenger", "url": Platform::Messenger.url()}),
serde_json::json!({"name": "Facebook", "url": Platform::Facebook.url()}),
serde_json::json!({"name": "X", "url": Platform::X.url()}),
]
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_platform_urls() {
assert_eq!(Platform::Instagram.url(), "https://www.instagram.com/direct/inbox/");
assert_eq!(Platform::Messenger.url(), "https://www.messenger.com");
assert_eq!(Platform::Facebook.url(), "https://www.facebook.com/messages/");
assert_eq!(Platform::X.url(), "https://x.com/messages");
}

#[test]
fn test_platform_from_str() {
assert!(Platform::from_str("Instagram").is_some());
assert!(Platform::from_str("Messenger").is_some());
assert!(Platform::from_str("Facebook").is_some());
assert!(Platform::from_str("X").is_some());
assert!(Platform::from_str("TikTok").is_none());
assert!(Platform::from_str("").is_none());
}

#[test]
fn test_platform_names() {
assert_eq!(Platform::Instagram.name(), "Instagram");
assert_eq!(Platform::X.name(), "X");
}
}
Loading