refactor: cleanup

This commit is contained in:
zhom
2026-06-02 04:22:38 +04:00
parent b4624f8e8f
commit 14522c75f6
2 changed files with 35 additions and 30 deletions
+27 -28
View File
@@ -586,22 +586,12 @@ pub async fn get_api_server_status() -> Result<Option<u16>, String> {
Ok(server_guard.get_port()) Ok(server_guard.get_port())
} }
/// Serialize a browser config (camoufox/wayfern) to JSON for an API response, /// Serialize a browser config (camoufox/wayfern) to JSON for an API response.
/// dropping the `fingerprint` field unless the user has an active paid plan. /// Viewing a profile's fingerprint is available to every API caller; only
/// Viewing fingerprints is a paid feature, so free users (and unauthenticated /// editing it (via `update_profile`) and launching/killing profiles
/// API/MCP callers) must never receive it. `is_paid` is resolved once per /// programmatically require an active paid plan.
/// handler via `has_active_paid_subscription()`. fn config_to_api_value<T: serde::Serialize>(config: Option<&T>) -> Option<serde_json::Value> {
fn config_to_api_value<T: serde::Serialize>( serde_json::to_value(config?).ok()
config: Option<&T>,
is_paid: bool,
) -> Option<serde_json::Value> {
let mut value = serde_json::to_value(config?).ok()?;
if !is_paid {
if let Some(obj) = value.as_object_mut() {
obj.remove("fingerprint");
}
}
Some(value)
} }
// API Handlers - Profiles // API Handlers - Profiles
@@ -620,9 +610,6 @@ fn config_to_api_value<T: serde::Serialize>(
)] )]
async fn get_profiles() -> Result<Json<ApiProfilesResponse>, StatusCode> { async fn get_profiles() -> Result<Json<ApiProfilesResponse>, StatusCode> {
let profile_manager = ProfileManager::instance(); let profile_manager = ProfileManager::instance();
let is_paid = crate::cloud_auth::CLOUD_AUTH
.has_active_paid_subscription()
.await;
match profile_manager.list_profiles() { match profile_manager.list_profiles() {
Ok(profiles) => { Ok(profiles) => {
let api_profiles: Vec<ApiProfile> = profiles let api_profiles: Vec<ApiProfile> = profiles
@@ -637,7 +624,7 @@ async fn get_profiles() -> Result<Json<ApiProfilesResponse>, StatusCode> {
process_id: profile.process_id, process_id: profile.process_id,
last_launch: profile.last_launch, last_launch: profile.last_launch,
release_type: profile.release_type.clone(), release_type: profile.release_type.clone(),
camoufox_config: config_to_api_value(profile.camoufox_config.as_ref(), is_paid), camoufox_config: config_to_api_value(profile.camoufox_config.as_ref()),
group_id: profile.group_id.clone(), group_id: profile.group_id.clone(),
tags: profile.tags.clone(), tags: profile.tags.clone(),
is_running: profile.process_id.is_some(), // Simple check based on process_id is_running: profile.process_id.is_some(), // Simple check based on process_id
@@ -677,9 +664,6 @@ async fn get_profile(
State(_state): State<ApiServerState>, State(_state): State<ApiServerState>,
) -> Result<Json<ApiProfileResponse>, StatusCode> { ) -> Result<Json<ApiProfileResponse>, StatusCode> {
let profile_manager = ProfileManager::instance(); let profile_manager = ProfileManager::instance();
let is_paid = crate::cloud_auth::CLOUD_AUTH
.has_active_paid_subscription()
.await;
match profile_manager.list_profiles() { match profile_manager.list_profiles() {
Ok(profiles) => { Ok(profiles) => {
if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == id) { if let Some(profile) = profiles.iter().find(|p| p.id.to_string() == id) {
@@ -694,7 +678,7 @@ async fn get_profile(
process_id: profile.process_id, process_id: profile.process_id,
last_launch: profile.last_launch, last_launch: profile.last_launch,
release_type: profile.release_type.clone(), release_type: profile.release_type.clone(),
camoufox_config: config_to_api_value(profile.camoufox_config.as_ref(), is_paid), camoufox_config: config_to_api_value(profile.camoufox_config.as_ref()),
group_id: profile.group_id.clone(), group_id: profile.group_id.clone(),
tags: profile.tags.clone(), tags: profile.tags.clone(),
is_running: profile.process_id.is_some(), // Simple check based on process_id is_running: profile.process_id.is_some(), // Simple check based on process_id
@@ -730,9 +714,6 @@ async fn create_profile(
Json(request): Json<CreateProfileRequest>, Json(request): Json<CreateProfileRequest>,
) -> Result<Json<ApiProfileResponse>, StatusCode> { ) -> Result<Json<ApiProfileResponse>, StatusCode> {
let profile_manager = ProfileManager::instance(); let profile_manager = ProfileManager::instance();
let is_paid = crate::cloud_auth::CLOUD_AUTH
.has_active_paid_subscription()
.await;
// Parse camoufox config if provided // Parse camoufox config if provided
let camoufox_config = if let Some(config) = &request.camoufox_config { let camoufox_config = if let Some(config) = &request.camoufox_config {
@@ -809,7 +790,7 @@ async fn create_profile(
process_id: profile.process_id, process_id: profile.process_id,
last_launch: profile.last_launch, last_launch: profile.last_launch,
release_type: profile.release_type, release_type: profile.release_type,
camoufox_config: config_to_api_value(profile.camoufox_config.as_ref(), is_paid), camoufox_config: config_to_api_value(profile.camoufox_config.as_ref()),
group_id: profile.group_id, group_id: profile.group_id,
tags: profile.tags, tags: profile.tags,
is_running: false, is_running: false,
@@ -914,6 +895,14 @@ async fn update_profile(
} }
if let Some(camoufox_config) = request.camoufox_config { if let Some(camoufox_config) = request.camoufox_config {
// Editing a profile's fingerprint config is a paid feature everywhere
// (GUI, API, MCP). Viewing it is free; mutating it is not.
if !crate::cloud_auth::CLOUD_AUTH
.has_active_paid_subscription()
.await
{
return Err(StatusCode::PAYMENT_REQUIRED);
}
let config: Result<CamoufoxConfig, _> = serde_json::from_value(camoufox_config); let config: Result<CamoufoxConfig, _> = serde_json::from_value(camoufox_config);
match config { match config {
Ok(config) => { Ok(config) => {
@@ -1844,6 +1833,7 @@ async fn open_url_in_profile(
responses( responses(
(status = 204, description = "Browser process killed successfully"), (status = 204, description = "Browser process killed successfully"),
(status = 401, description = "Unauthorized"), (status = 401, description = "Unauthorized"),
(status = 402, description = "Active paid plan required"),
(status = 404, description = "Profile not found"), (status = 404, description = "Profile not found"),
(status = 500, description = "Internal server error") (status = 500, description = "Internal server error")
), ),
@@ -1856,6 +1846,15 @@ async fn kill_profile(
Path(id): Path<String>, Path(id): Path<String>,
State(state): State<ApiServerState>, State(state): State<ApiServerState>,
) -> Result<StatusCode, StatusCode> { ) -> Result<StatusCode, StatusCode> {
// Programmatically launching and stopping profiles is a paid feature; the
// run/open-url handlers gate the same way.
if !crate::cloud_auth::CLOUD_AUTH
.has_active_paid_subscription()
.await
{
return Err(StatusCode::PAYMENT_REQUIRED);
}
let profile_manager = ProfileManager::instance(); let profile_manager = ProfileManager::instance();
let profiles = profile_manager let profiles = profile_manager
.list_profiles() .list_profiles()
+8 -2
View File
@@ -508,7 +508,7 @@ impl McpServer {
}, },
McpTool { McpTool {
name: "run_profile".to_string(), name: "run_profile".to_string(),
description: "Launch a browser profile with an optional URL".to_string(), description: "Launch a browser profile with an optional URL. Requires an active Pro subscription.".to_string(),
input_schema: serde_json::json!({ input_schema: serde_json::json!({
"type": "object", "type": "object",
"properties": { "properties": {
@@ -530,7 +530,7 @@ impl McpServer {
}, },
McpTool { McpTool {
name: "kill_profile".to_string(), name: "kill_profile".to_string(),
description: "Stop a running browser profile".to_string(), description: "Stop a running browser profile. Requires an active Pro subscription.".to_string(),
input_schema: serde_json::json!({ input_schema: serde_json::json!({
"type": "object", "type": "object",
"properties": { "properties": {
@@ -1829,6 +1829,9 @@ impl McpServer {
&self, &self,
arguments: &serde_json::Value, arguments: &serde_json::Value,
) -> Result<serde_json::Value, McpError> { ) -> Result<serde_json::Value, McpError> {
// Launching profiles programmatically is a paid feature.
Self::require_paid_subscription("Launching a profile").await?;
let profile_id = arguments let profile_id = arguments
.get("profile_id") .get("profile_id")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
@@ -1910,6 +1913,9 @@ impl McpServer {
&self, &self,
arguments: &serde_json::Value, arguments: &serde_json::Value,
) -> Result<serde_json::Value, McpError> { ) -> Result<serde_json::Value, McpError> {
// Stopping profiles programmatically is a paid feature.
Self::require_paid_subscription("Killing a profile").await?;
let profile_id = arguments let profile_id = arguments
.get("profile_id") .get("profile_id")
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())