mod adblock; mod browser; mod credentials; mod library; mod board; mod color_tools; mod persistence; mod sessions; mod study; mod downloads; mod settings; mod state; mod history; mod projects; mod refs_format; use crate::state::AppState; use tauri::{Emitter, Manager}; use tauri_plugin_sql::{Migration, MigrationKind}; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_clipboard_manager::init()) .plugin(tauri_plugin_sql::Builder::default().add_migrations("sqlite:muse_alpha_v2.db", migrations()).build()) .plugin(tauri_plugin_stronghold::Builder::new(|password| { let salt = b"muse-vault-kdf-salt-2026-v1-prod"; let mut key = [0u8; 32]; argon2::Argon2::default().hash_password_into(password.as_bytes(), salt, &mut key).expect("argon2 KDF failed"); key.to_vec() }).build()) .register_uri_scheme_protocol("muse-action", |ctx, request| { let uri = request.uri().to_string(); let app = ctx.app_handle().clone(); let action = uri.split("://").nth(1).unwrap_or("").split('?').next().unwrap_or("").trim_end_matches('/').to_string(); let query = uri.split('?').nth(1).unwrap_or(""); let params: std::collections::HashMap = query.split('&').filter_map(|pair| { let (k, v) = pair.split_once('=')?; Some((percent_decode(k), percent_decode(v))) }).collect(); match action.as_str() { "board" | "library" => {} "vault" => { let va = params.get("action").cloned().unwrap_or_default(); match va.as_str() { "save-prompt" => { let _ = app.emit("vault://save-prompt", serde_json::json!({"origin": params.get("origin").cloned().unwrap_or_default(), "username": params.get("username").cloned().unwrap_or_default(), "password": params.get("password").cloned().unwrap_or_default()})); } "has-login-form" => { let _ = app.emit("vault://login-detected", serde_json::json!({"origin": params.get("origin").cloned().unwrap_or_default(), "fields": params.get("fields").cloned().unwrap_or_default()})); } _ => {} } } _ => {} } tauri::http::Response::builder().status(200).header("Access-Control-Allow-Origin", "*").body(Vec::new()).unwrap() }) .manage(state::AppState::default()) .manage(adblock::engine::AdBlockState::new()) .manage(library::LibraryState::default()) .manage(board::BoardState::default()) .manage(downloads::DownloadState::default()) .manage(study::StudyState::default()) .invoke_handler(tauri::generate_handler![ settings::phase0_status, settings::board_load_state, settings::board_save_state, settings::board_export_file, settings::board_import_file, settings::screen_capture_full, settings::screen_capture_region, settings::screen_capture_window_region, history::history_list, history::history_clear, refs_format::refs_export, refs_format::refs_import, projects::projects_list, projects::projects_get_active_id, projects::project_create, projects::project_save, projects::project_load, projects::project_delete, projects::project_rename, browser::capture::browser_capture_viewport, browser::capture::browser_capture_clip, browser::capture::browser_capture_full_page, browser::autofill::tab_autofill, browser::commands::browser_init, browser::commands::browser_set_visible, browser::commands::browser_hide_all, browser::commands::tab_create, browser::commands::tab_activate, browser::commands::tab_close, browser::commands::tab_restore, browser::commands::tab_navigate, browser::commands::tab_reload, browser::commands::tab_back, browser::commands::tab_forward, browser::commands::tab_zoom, browser::commands::tab_resize, browser::commands::tab_get_all, browser::commands::tab_pin, browser::commands::tab_find, browser::commands::tab_find_clear, browser::context_menu::browser_context_menu, adblock::commands::shield_get_report, adblock::commands::shield_check_url, adblock::commands::shield_cosmetic_css, adblock::commands::shield_toggle_domain, adblock::commands::shield_is_allowed, adblock::commands::shield_update_lists, adblock::commands::shield_add_user_rule, adblock::commands::shield_list_subscriptions, library::library_add_item, library::library_import_local, library::library_import_data_url, library::library_update_metadata, library::library_remove_tag, library::library_load, library::library_items, library::library_search, library::library_remove_item, library::library_add_tag, board::board_list, board::board_current, board::board_create, board::board_open, board::board_save_as, board::board_load, board::board_items, board::board_add_image, board::board_add_note, board::board_add_palette, board::board_extract_palette_from_item, board::board_add_palette_from_item, board::board_update_item, board::board_delete_item, persistence::storage_info, persistence::storage_clear_library, persistence::storage_clear_projects, persistence::storage_reveal_folder, sessions::sessions_save, sessions::sessions_load, sessions::sessions_list, sessions::sessions_auto_save, sessions::sessions_delete, sessions::sessions_rename, downloads::downloads_list, downloads::downloads_clear_completed, downloads::download_to_library, downloads::web_clip_page, credentials::credentials_list, credentials::credentials_generate_password, study::study_start, study::study_complete, study::study_list, color_tools::color_export, color_tools::color_search_library, ]) .setup(|app| { #[cfg(desktop)] app.handle().plugin(tauri_plugin_global_shortcut::Builder::new().build())?; if let Ok(zoom_mem) = crate::persistence::load_json::>(app.handle(), "zoom_memory.json") { if !zoom_mem.is_empty() { let app_state = app.state::(); let mut tabs = app_state.tabs.lock().expect("tabs lock"); tabs.zoom_memory = zoom_mem; } } adblock::updater::spawn_updater(app.handle().clone()); Ok(()) }) .run(tauri::generate_context!()) .expect("error while running Refstudio"); } fn migrations() -> Vec { vec![ Migration { version: 1, description: "phase0_init", sql: include_str!("../migrations/001_phase0_init.sql"), kind: MigrationKind::Up }, Migration { version: 2, description: "phase3_tables", sql: include_str!("../migrations/002_phase3_tables.sql"), kind: MigrationKind::Up } ] } fn percent_decode(s: &str) -> String { let bytes = s.as_bytes(); let mut out = Vec::with_capacity(bytes.len()); let mut i = 0; while i < bytes.len() { if bytes[i] == b'%' && i + 2 < bytes.len() { if let Ok(hex) = std::str::from_utf8(&bytes[i+1..i+3]) { if let Ok(v) = u8::from_str_radix(hex, 16) { out.push(v); i += 3; continue; } } } out.push(if bytes[i] == b'+' { b' ' } else { bytes[i] }); i += 1; } String::from_utf8_lossy(&out).to_string() }