diff --git a/.gitignore b/.gitignore index db9ab16..dc32731 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ dist-ssr *.sw? .nuxt src-tauri/target -nogit* \ No newline at end of file +nogit* +.claude \ No newline at end of file diff --git a/src-tauri/src/extension/core/protocol.rs b/src-tauri/src/extension/core/protocol.rs index bc78bdf..438a5d6 100644 --- a/src-tauri/src/extension/core/protocol.rs +++ b/src-tauri/src/extension/core/protocol.rs @@ -6,7 +6,6 @@ use crate::AppState; use mime; use serde::Deserialize; use serde::Serialize; -use std::collections::HashMap; use std::fmt; use std::fs; use std::path::PathBuf; @@ -15,9 +14,11 @@ use tauri::http::Uri; use tauri::http::{Request, Response}; use tauri::{AppHandle, State}; -// Cache for modified HTML files (extension_id -> modified content) +// Extension protocol name constant +pub const EXTENSION_PROTOCOL_NAME: &str = "haex-extension"; + +// Cache for extension info (used for asset loading without origin header) lazy_static::lazy_static! { - static ref HTML_CACHE: Mutex>> = Mutex::new(HashMap::new()); static ref EXTENSION_CACHE: Mutex> = Mutex::new(None); } @@ -86,7 +87,7 @@ impl From for DataProcessingError { pub fn resolve_secure_extension_asset_path( app_handle: &AppHandle, - state: State, + state: &State, key_hash: &str, extension_name: &str, extension_version: &str, @@ -179,10 +180,10 @@ pub fn extension_protocol_handler( .and_then(|v| v.to_str().ok()) .unwrap_or(""); - // Only allow same-protocol requests (haex-extension://) or tauri origin + // Only allow same-protocol requests or tauri origin // For null/empty origin (initial load), use wildcard - let allowed_origin = if origin.starts_with("haex-extension://") || origin == get_tauri_origin() - { + let protocol_prefix = format!("{}://", EXTENSION_PROTOCOL_NAME); + let allowed_origin = if origin.starts_with(&protocol_prefix) || origin == get_tauri_origin() { origin } else if origin.is_empty() || origin == "null" { "*" // Allow initial load without origin @@ -217,19 +218,6 @@ pub fn extension_protocol_handler( println!("Origin: {}", origin); println!("Referer: {}", referer); - /* let encoded_info = - match parse_encoded_info_from_origin_or_uri_or_referer(&origin, uri_ref, &referer) { - Ok(info) => info, - Err(e) => { - eprintln!("Fehler beim Parsen des Origin für Extension-Info: {}", e); - return Response::builder() - .status(400) - .header("Access-Control-Allow-Origin", allowed_origin) - .body(Vec::from(format!("Ungültiger Origin: {}", e))) - .map_err(|e| e.into()); - } - }; */ - let info = match parse_encoded_info_from_origin_or_uri_or_referer_or_cache(&origin, uri_ref, &referer) { @@ -261,30 +249,12 @@ pub fn extension_protocol_handler( let resource_segments: Vec<&str> = segments_iter.collect(); let raw_asset_path = resource_segments.join("/"); - // Handle SPA routing - serve index.html for all non-asset paths + // Simple asset loading: if path is empty, serve index.html, otherwise try to load the asset + // This is framework-agnostic and lets the file system determine if it exists let asset_to_load = if raw_asset_path.is_empty() { "index.html" - } else if raw_asset_path.starts_with("_nuxt/") - || raw_asset_path.ends_with(".js") - || raw_asset_path.ends_with(".css") - || raw_asset_path.ends_with(".json") - || raw_asset_path.ends_with(".ico") - || raw_asset_path.ends_with(".txt") - || raw_asset_path.ends_with(".svg") - || raw_asset_path.ends_with(".png") - || raw_asset_path.ends_with(".jpg") - || raw_asset_path.ends_with(".jpeg") - || raw_asset_path.ends_with(".gif") - || raw_asset_path.ends_with(".woff") - || raw_asset_path.ends_with(".woff2") - || raw_asset_path.ends_with(".ttf") - || raw_asset_path.ends_with(".eot") - { - // Serve actual asset - &raw_asset_path } else { - // SPA fallback - serve index.html for routes - "index.html" + &raw_asset_path }; println!("Path: {}", path_str); @@ -561,7 +531,7 @@ pub fn extension_protocol_handler( let absolute_secure_path = resolve_secure_extension_asset_path( app_handle, - state, + &state, &info.key_hash, &info.name, &info.version, @@ -573,307 +543,13 @@ pub fn extension_protocol_handler( if absolute_secure_path.exists() && absolute_secure_path.is_file() { match fs::read(&absolute_secure_path) { - Ok(mut content) => { + Ok(content) => { let mime_type = mime_guess::from_path(&absolute_secure_path) .first_or(mime::APPLICATION_OCTET_STREAM) .to_string(); - // Für index.html – injiziere Tag + localStorage-Polyfill - if asset_to_load == "index.html" && mime_type.contains("html") { - // Cache-Key erstellen (extension-host + asset) - let host = uri_ref - .host() - .map_or("unknown".to_string(), |h| h.to_string()); - let cache_key = format!("{}_{}", host, asset_to_load); - - // Cache checken (aus deinem alten Code) - if let Ok(cache_guard) = HTML_CACHE.lock() { - if let Some(cached_content) = cache_guard.get(&cache_key) { - println!("Serving cached HTML for: {}", cache_key); - let content_length = cached_content.len(); - return Response::builder() - .status(200) - .header("Content-Type", &mime_type) - .header("Content-Length", content_length.to_string()) - .header("Accept-Ranges", "bytes") - .header("X-HaexHub-Cache", "HIT") - .header("Access-Control-Allow-Origin", allowed_origin) - .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") - .header("Access-Control-Allow-Headers", "*") - .header("Access-Control-Allow-Credentials", "true") - .body(cached_content.clone()) - .map_err(|e| e.into()); - } - } - - // Nicht gecacht: Modifiziere HTML - if let Ok(html_str) = String::from_utf8(content.clone()) { - // 1. Polyfill injizieren (als ERSTES in ) - let polyfill_script = r#""#; - - // 2. Base-Tag erstellen - let base_tag = format!(r#""#, encode_hex_for_log(&info)); - - // 3. Beide in injizieren: Polyfill zuerst, dann Base-Tag - let modified_html = if let Some(head_pos) = html_str.find("") { - let insert_pos = head_pos + 6; // Nach - format!( - "{}{}{}{}", - &html_str[..insert_pos], - polyfill_script, - base_tag, - &html_str[insert_pos..] - ) - } else { - // Kein gefunden - prepend - format!("{}{}{}", polyfill_script, base_tag, html_str) - }; - - content = modified_html.into_bytes(); - - // Cache die modifizierte HTML (aus deinem alten Code) - if let Ok(mut cache_guard) = HTML_CACHE.lock() { - cache_guard.insert(cache_key, content.clone()); - println!("Cached modified HTML for future requests"); - } - } - } + // Note: Base tag and polyfills are now injected by the SDK at runtime + // No server-side HTML modification needed let content_length = content.len(); println!( @@ -887,14 +563,6 @@ pub fn extension_protocol_handler( .header("Content-Type", &mime_type) .header("Content-Length", content_length.to_string()) .header("Accept-Ranges", "bytes") - .header( - "X-HaexHub-Cache", - if asset_to_load == "index.html" && mime_type.contains("html") { - "MISS" - } else { - "N/A" - }, - ) .header("Access-Control-Allow-Origin", allowed_origin) .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") .header("Access-Control-Allow-Headers", "*") @@ -925,6 +593,51 @@ pub fn extension_protocol_handler( } } } else { + // Asset not found - try index.html fallback for SPA routing + // This allows client-side routing to work (e.g., /settings -> index.html) + if asset_to_load != "index.html" { + eprintln!( + "Asset nicht gefunden: {}, versuche index.html fallback für SPA routing", + absolute_secure_path.display() + ); + + let index_path = resolve_secure_extension_asset_path( + app_handle, + &state, + &info.key_hash, + &info.name, + &info.version, + "index.html", + )?; + + if index_path.exists() && index_path.is_file() { + match fs::read(&index_path) { + Ok(content) => { + let mime_type = "text/html"; + + // Note: Base tag and polyfills are injected by SDK at runtime + + let content_length = content.len(); + return Response::builder() + .status(200) + .header("Content-Type", mime_type) + .header("Content-Length", content_length.to_string()) + .header("Accept-Ranges", "bytes") + .header("Access-Control-Allow-Origin", allowed_origin) + .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + .header("Access-Control-Allow-Headers", "*") + .header("Access-Control-Allow-Credentials", "true") + .body(content) + .map_err(|e| e.into()); + } + Err(_) => { + // Fall through to 404 + } + } + } + } + + // No fallback available - return 404 eprintln!( "Asset nicht gefunden oder ist kein File: {}", absolute_secure_path.display() diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 021d5da..57b3ce4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,10 +17,10 @@ pub struct AppState { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - let protocol_name = "haex-extension"; + use extension::core::EXTENSION_PROTOCOL_NAME; tauri::Builder::default() - .register_uri_scheme_protocol(protocol_name, move |context, request| { + .register_uri_scheme_protocol(EXTENSION_PROTOCOL_NAME, move |context, request| { // Hole den AppState aus dem Context let app_handle = context.app_handle(); let state = app_handle.state::(); diff --git a/src/config/constants.ts b/src/config/constants.ts new file mode 100644 index 0000000..f32d3f9 --- /dev/null +++ b/src/config/constants.ts @@ -0,0 +1,14 @@ +/** + * Application-wide constants + */ + +/** + * The custom protocol name used for extensions + * Must match EXTENSION_PROTOCOL_NAME in Rust (src-tauri/src/extension/core/protocol.rs) + */ +export const EXTENSION_PROTOCOL_NAME = 'haex-extension' + +/** + * Build the full protocol prefix (e.g., "haex-extension://") + */ +export const EXTENSION_PROTOCOL_PREFIX = `${EXTENSION_PROTOCOL_NAME}://` diff --git a/src/pages/vault/[vaultId]/extensions/[extensionId].vue b/src/pages/vault/[vaultId]/extensions/[extensionId].vue index 565b316..8213d7c 100644 --- a/src/pages/vault/[vaultId]/extensions/[extensionId].vue +++ b/src/pages/vault/[vaultId]/extensions/[extensionId].vue @@ -68,6 +68,7 @@ import { import { useExtensionTabsStore } from '~/stores/extensions/tabs' import type { IHaexHubExtension } from '~/types/haexhub' import { platform } from '@tauri-apps/plugin-os' +import { EXTENSION_PROTOCOL_NAME, EXTENSION_PROTOCOL_PREFIX } from '~/config/constants' definePageMeta({ name: 'haexExtension', @@ -151,10 +152,10 @@ const getExtensionUrl = (extension: IHaexHubExtension) => { if (os === 'android' || os === 'windows') { // Android/Windows: http://.localhost/path - schemeUrl = `http://haex-extension.localhost/${encodedInfo}/index.html` + schemeUrl = `http://${EXTENSION_PROTOCOL_NAME}.localhost/${encodedInfo}/index.html` } else { // macOS/Linux/iOS: Klassisch scheme://localhost/path - schemeUrl = `haex-extension://localhost/${encodedInfo}/index.html` + schemeUrl = `${EXTENSION_PROTOCOL_PREFIX}localhost/${encodedInfo}/index.html` } return schemeUrl diff --git a/src/stores/extensions/index.ts b/src/stores/extensions/index.ts index 165550c..3f487bb 100644 --- a/src/stores/extensions/index.ts +++ b/src/stores/extensions/index.ts @@ -1,5 +1,6 @@ import { invoke } from '@tauri-apps/api/core' import { readFile } from '@tauri-apps/plugin-fs' +import { EXTENSION_PROTOCOL_PREFIX } from '~/config/constants' import type { IHaexHubExtension, @@ -86,7 +87,7 @@ export const useExtensionsStore = defineStore('extensionsStore', () => { currentExtension.value.version, ) - return `haex-extension://localhost/${encodedInfo}/index.html` + return `${EXTENSION_PROTOCOL_PREFIX}localhost/${encodedInfo}/index.html` }) /* const getExtensionPathAsync = async (