mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
polyfill for spa added. works now on android
This commit is contained in:
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@ -1696,6 +1696,7 @@ dependencies = [
|
||||
"ed25519-dalek",
|
||||
"fs_extra",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"rusqlite",
|
||||
|
||||
@ -32,6 +32,7 @@ base64 = "0.22"
|
||||
ed25519-dalek = "2.1"
|
||||
fs_extra = "1.3.0"
|
||||
hex = "0.4"
|
||||
lazy_static = "1.5"
|
||||
mime = "0.3"
|
||||
mime_guess = "2.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
@ -3,4 +3,4 @@
|
||||
/**
|
||||
* Definiert Aktionen, die auf eine Datenbank angewendet werden können.
|
||||
*/
|
||||
export type DbAction = "read" | "readwrite" | "create" | "delete" | "alterdrop";
|
||||
export type DbAction = "read" | "readWrite" | "create" | "delete" | "alterDrop";
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ExtensionInfoResponse = { key_hash: string, name: string, full_id: string, version: string, display_name: string | null, namespace: string | null, allowed_origin: string, };
|
||||
export type ExtensionInfoResponse = { keyHash: string, name: string, fullId: string, version: string, displayName: string | null, namespace: string | null, allowedOrigin: string, };
|
||||
|
||||
@ -3,4 +3,4 @@
|
||||
/**
|
||||
* Definiert Aktionen, die auf das Dateisystem angewendet werden können.
|
||||
*/
|
||||
export type FsAction = "read" | "readwrite";
|
||||
export type FsAction = "read" | "readWrite";
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -2270,12 +2270,6 @@
|
||||
"Identifier": {
|
||||
"description": "Permission identifier",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
"const": "android-fs:default",
|
||||
"markdownDescription": "Default permissions for the plugin"
|
||||
},
|
||||
{
|
||||
"description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`",
|
||||
"type": "string",
|
||||
|
||||
@ -2270,12 +2270,6 @@
|
||||
"Identifier": {
|
||||
"description": "Permission identifier",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
"const": "android-fs:default",
|
||||
"markdownDescription": "Default permissions for the plugin"
|
||||
},
|
||||
{
|
||||
"description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`",
|
||||
"type": "string",
|
||||
|
||||
@ -687,23 +687,36 @@ impl CrdtTransformer {
|
||||
insert_stmt: &mut Insert,
|
||||
timestamp: &Timestamp,
|
||||
) -> Result<(), DatabaseError> {
|
||||
// Add both haex_timestamp and haex_tombstone columns
|
||||
insert_stmt
|
||||
.columns
|
||||
.push(Ident::new(self.columns.hlc_timestamp));
|
||||
insert_stmt
|
||||
.columns
|
||||
.push(Ident::new(self.columns.tombstone));
|
||||
|
||||
match insert_stmt.source.as_mut() {
|
||||
Some(query) => match &mut *query.body {
|
||||
SetExpr::Values(values) => {
|
||||
for row in &mut values.rows {
|
||||
// Add haex_timestamp value
|
||||
row.push(Expr::Value(
|
||||
Value::SingleQuotedString(timestamp.to_string()).into(),
|
||||
));
|
||||
// Add haex_tombstone value (0 = not deleted)
|
||||
row.push(Expr::Value(
|
||||
Value::Number("0".to_string(), false).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
SetExpr::Select(select) => {
|
||||
let hlc_expr =
|
||||
Expr::Value(Value::SingleQuotedString(timestamp.to_string()).into());
|
||||
select.projection.push(SelectItem::UnnamedExpr(hlc_expr));
|
||||
// Add haex_tombstone value (0 = not deleted)
|
||||
let tombstone_expr =
|
||||
Expr::Value(Value::Number("0".to_string(), false).into());
|
||||
select.projection.push(SelectItem::UnnamedExpr(tombstone_expr));
|
||||
}
|
||||
_ => {
|
||||
return Err(DatabaseError::UnsupportedStatement {
|
||||
|
||||
@ -8,10 +8,11 @@ use crate::extension::database::executor::SqlExecutor;
|
||||
use crate::extension::error::ExtensionError;
|
||||
use crate::extension::permissions::manager::PermissionManager;
|
||||
use crate::extension::permissions::types::ExtensionPermission;
|
||||
use crate::table_names::TABLE_EXTENSIONS;
|
||||
use crate::table_names::{TABLE_EXTENSIONS, TABLE_EXTENSION_PERMISSIONS};
|
||||
use crate::AppState;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@ -33,6 +34,7 @@ pub struct MissingExtension {
|
||||
}
|
||||
|
||||
struct ExtensionDataFromDb {
|
||||
full_extension_id: String,
|
||||
manifest: ExtensionManifest,
|
||||
enabled: bool,
|
||||
}
|
||||
@ -64,19 +66,19 @@ impl ExtensionManager {
|
||||
|
||||
/// Extrahiert eine Extension-ZIP-Datei und validiert das Manifest
|
||||
fn extract_and_validate_extension(
|
||||
source_path: &str,
|
||||
bytes: Vec<u8>,
|
||||
temp_prefix: &str,
|
||||
) -> Result<ExtractedExtension, ExtensionError> {
|
||||
let source = PathBuf::from(source_path);
|
||||
let temp = std::env::temp_dir().join(format!("{}_{}", temp_prefix, uuid::Uuid::new_v4()));
|
||||
|
||||
std::fs::create_dir_all(&temp).map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
fs::create_dir_all(&temp)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(temp.display().to_string(), e))?;
|
||||
|
||||
let file = File::open(&source).map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
let mut archive =
|
||||
ZipArchive::new(file).map_err(|e| ExtensionError::InstallationFailed {
|
||||
let mut archive = ZipArchive::new(Cursor::new(bytes)).map_err(|e| {
|
||||
ExtensionError::InstallationFailed {
|
||||
reason: format!("Invalid ZIP: {}", e),
|
||||
})?;
|
||||
}
|
||||
})?;
|
||||
|
||||
archive
|
||||
.extract(&temp)
|
||||
@ -84,7 +86,30 @@ impl ExtensionManager {
|
||||
reason: format!("Cannot extract ZIP: {}", e),
|
||||
})?;
|
||||
|
||||
// Check if manifest.json is directly in temp or in a subdirectory
|
||||
let manifest_path = temp.join("manifest.json");
|
||||
let actual_dir = if manifest_path.exists() {
|
||||
temp.clone()
|
||||
} else {
|
||||
// manifest.json is in a subdirectory - find it
|
||||
let mut found_dir = None;
|
||||
for entry in fs::read_dir(&temp)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(temp.display().to_string(), e))?
|
||||
{
|
||||
let entry = entry.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() && path.join("manifest.json").exists() {
|
||||
found_dir = Some(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found_dir.ok_or_else(|| ExtensionError::ManifestError {
|
||||
reason: "manifest.json not found in extension archive".to_string(),
|
||||
})?
|
||||
};
|
||||
|
||||
let manifest_path = actual_dir.join("manifest.json");
|
||||
let manifest_content =
|
||||
std::fs::read_to_string(&manifest_path).map_err(|e| ExtensionError::ManifestError {
|
||||
reason: format!("Cannot read manifest: {}", e),
|
||||
@ -92,14 +117,14 @@ impl ExtensionManager {
|
||||
|
||||
let manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
||||
|
||||
let content_hash = ExtensionCrypto::hash_directory(&temp).map_err(|e| {
|
||||
let content_hash = ExtensionCrypto::hash_directory(&actual_dir).map_err(|e| {
|
||||
ExtensionError::SignatureVerificationFailed {
|
||||
reason: e.to_string(),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(ExtractedExtension {
|
||||
temp_dir: temp,
|
||||
temp_dir: actual_dir,
|
||||
manifest,
|
||||
content_hash,
|
||||
})
|
||||
@ -119,7 +144,8 @@ impl ExtensionManager {
|
||||
|
||||
// Sicherstellen, dass das Basisverzeichnis existiert
|
||||
if !path.exists() {
|
||||
fs::create_dir_all(&path).map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
fs::create_dir_all(&path)
|
||||
.map_err(|e| ExtensionError::filesystem_with_path(path.display().to_string(), e))?;
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
@ -145,9 +171,34 @@ impl ExtensionManager {
|
||||
app_handle: &AppHandle,
|
||||
full_extension_id: &str,
|
||||
) -> Result<PathBuf, ExtensionError> {
|
||||
// Parse full_extension_id: key_hash_name_version
|
||||
// Split on first underscore to get key_hash
|
||||
let first_underscore =
|
||||
full_extension_id
|
||||
.find('_')
|
||||
.ok_or_else(|| ExtensionError::ValidationError {
|
||||
reason: format!("Invalid full_extension_id format: {}", full_extension_id),
|
||||
})?;
|
||||
|
||||
let key_hash = &full_extension_id[..first_underscore];
|
||||
let rest = &full_extension_id[first_underscore + 1..];
|
||||
|
||||
// Split on last underscore to get version
|
||||
let last_underscore = rest
|
||||
.rfind('_')
|
||||
.ok_or_else(|| ExtensionError::ValidationError {
|
||||
reason: format!("Invalid full_extension_id format: {}", full_extension_id),
|
||||
})?;
|
||||
|
||||
let name = &rest[..last_underscore];
|
||||
let version = &rest[last_underscore + 1..];
|
||||
|
||||
// Build hierarchical path: key_hash/name/version/
|
||||
let specific_extension_dir = self
|
||||
.get_base_extension_dir(app_handle)?
|
||||
.join(full_extension_id);
|
||||
.join(key_hash)
|
||||
.join(name)
|
||||
.join(version);
|
||||
|
||||
Ok(specific_extension_dir)
|
||||
}
|
||||
@ -220,14 +271,44 @@ impl ExtensionManager {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remove_extension_by_full_id(
|
||||
&self,
|
||||
app_handle: &AppHandle,
|
||||
full_extension_id: &str,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<(), ExtensionError> {
|
||||
// Parse full_extension_id: key_hash_name_version
|
||||
// Since _ is not allowed in name and version, we can split safely
|
||||
let parts: Vec<&str> = full_extension_id.split('_').collect();
|
||||
|
||||
if parts.len() != 3 {
|
||||
return Err(ExtensionError::ValidationError {
|
||||
reason: format!(
|
||||
"Invalid full_extension_id format (expected 3 parts): {}",
|
||||
full_extension_id
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let key_hash = parts[0];
|
||||
let name = parts[1];
|
||||
let version = parts[2];
|
||||
|
||||
self.remove_extension_internal(app_handle, key_hash, name, version, state)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn remove_extension_internal(
|
||||
&self,
|
||||
app_handle: &AppHandle,
|
||||
key_hash: &str,
|
||||
extension_id: &str,
|
||||
extension_name: &str,
|
||||
extension_version: &str,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<(), ExtensionError> {
|
||||
// Erstelle full_extension_id: key_hash_name_version
|
||||
let full_extension_id = format!("{}_{}_{}",key_hash, extension_name, extension_version);
|
||||
|
||||
// Lösche Permissions und Extension-Eintrag in einer Transaktion
|
||||
with_connection(&state.db, |conn| {
|
||||
let tx = conn.transaction().map_err(DatabaseError::from)?;
|
||||
@ -236,31 +317,59 @@ impl ExtensionManager {
|
||||
reason: "Failed to lock HLC service".to_string(),
|
||||
})?;
|
||||
|
||||
// Lösche alle Permissions
|
||||
PermissionManager::delete_permissions_in_transaction(&tx, &hlc_service, extension_id)?;
|
||||
// Lösche alle Permissions mit full_extension_id
|
||||
PermissionManager::delete_permissions_in_transaction(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&full_extension_id,
|
||||
)?;
|
||||
|
||||
// Lösche Extension-Eintrag
|
||||
// Lösche Extension-Eintrag mit full_extension_id
|
||||
let sql = format!("DELETE FROM {} WHERE id = ?", TABLE_EXTENSIONS);
|
||||
SqlExecutor::execute_internal_typed(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&sql,
|
||||
rusqlite::params![extension_id],
|
||||
rusqlite::params![full_extension_id],
|
||||
)?;
|
||||
|
||||
tx.commit().map_err(DatabaseError::from)
|
||||
})?;
|
||||
|
||||
// Entferne aus dem In-Memory-Manager
|
||||
self.remove_extension(&extension_id)?;
|
||||
// Entferne aus dem In-Memory-Manager mit full_extension_id
|
||||
self.remove_extension(&full_extension_id)?;
|
||||
|
||||
// Lösche Dateien vom Dateisystem
|
||||
// Lösche nur den spezifischen Versions-Ordner: key_hash/name/version
|
||||
let extension_dir =
|
||||
self.get_extension_dir(app_handle, key_hash, extension_id, extension_version)?;
|
||||
self.get_extension_dir(app_handle, key_hash, extension_name, extension_version)?;
|
||||
|
||||
if extension_dir.exists() {
|
||||
std::fs::remove_dir_all(&extension_dir)
|
||||
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
std::fs::remove_dir_all(&extension_dir).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(extension_dir.display().to_string(), e)
|
||||
})?;
|
||||
|
||||
// Versuche, leere Parent-Ordner zu löschen
|
||||
// 1. Extension-Name-Ordner (key_hash/name)
|
||||
if let Some(name_dir) = extension_dir.parent() {
|
||||
if name_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(name_dir) {
|
||||
if entries.count() == 0 {
|
||||
let _ = std::fs::remove_dir(name_dir);
|
||||
|
||||
// 2. Key-Hash-Ordner (key_hash) - nur wenn auch leer
|
||||
if let Some(key_hash_dir) = name_dir.parent() {
|
||||
if key_hash_dir.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(key_hash_dir) {
|
||||
if entries.count() == 0 {
|
||||
let _ = std::fs::remove_dir(key_hash_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -268,9 +377,9 @@ impl ExtensionManager {
|
||||
|
||||
pub async fn preview_extension_internal(
|
||||
&self,
|
||||
source_path: String,
|
||||
file_bytes: Vec<u8>,
|
||||
) -> Result<ExtensionPreview, ExtensionError> {
|
||||
let extracted = Self::extract_and_validate_extension(&source_path, "haexhub_preview")?;
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_preview")?;
|
||||
|
||||
let is_valid_signature = ExtensionCrypto::verify_signature(
|
||||
&extracted.manifest.public_key,
|
||||
@ -293,11 +402,11 @@ impl ExtensionManager {
|
||||
pub async fn install_extension_with_permissions_internal(
|
||||
&self,
|
||||
app_handle: AppHandle,
|
||||
source_path: String,
|
||||
file_bytes: Vec<u8>,
|
||||
custom_permissions: EditablePermissions,
|
||||
state: &State<'_, AppState>,
|
||||
) -> Result<String, ExtensionError> {
|
||||
let extracted = Self::extract_and_validate_extension(&source_path, "haexhub_ext")?;
|
||||
let extracted = Self::extract_and_validate_extension(file_bytes, "haexhub_ext")?;
|
||||
|
||||
// Signatur verifizieren (bei Installation wird ein Fehler geworfen, nicht nur geprüft)
|
||||
ExtensionCrypto::verify_signature(
|
||||
@ -316,17 +425,95 @@ impl ExtensionManager {
|
||||
&extracted.manifest.version,
|
||||
)?;
|
||||
|
||||
std::fs::create_dir_all(&extensions_dir)
|
||||
.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
std::fs::create_dir_all(&extensions_dir).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(extensions_dir.display().to_string(), e)
|
||||
})?;
|
||||
|
||||
copy_directory(
|
||||
extracted.temp_dir.to_string_lossy().to_string(),
|
||||
extensions_dir.to_string_lossy().to_string(),
|
||||
)?;
|
||||
// Copy contents of extracted.temp_dir to extensions_dir
|
||||
// Note: extracted.temp_dir already points to the correct directory with manifest.json
|
||||
for entry in fs::read_dir(&extracted.temp_dir).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(extracted.temp_dir.display().to_string(), e)
|
||||
})? {
|
||||
let entry = entry.map_err(|e| ExtensionError::Filesystem { source: e })?;
|
||||
let path = entry.path();
|
||||
let file_name = entry.file_name();
|
||||
let dest_path = extensions_dir.join(&file_name);
|
||||
|
||||
if path.is_dir() {
|
||||
copy_directory(
|
||||
path.to_string_lossy().to_string(),
|
||||
dest_path.to_string_lossy().to_string(),
|
||||
)?;
|
||||
} else {
|
||||
fs::copy(&path, &dest_path).map_err(|e| {
|
||||
ExtensionError::filesystem_with_path(path.display().to_string(), e)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
let permissions = custom_permissions.to_internal_permissions(&full_extension_id);
|
||||
|
||||
PermissionManager::save_permissions(state, &permissions).await?;
|
||||
// Extension-Eintrag und Permissions in einer Transaktion speichern
|
||||
with_connection(&state.db, |conn| {
|
||||
let tx = conn.transaction().map_err(DatabaseError::from)?;
|
||||
|
||||
let hlc_service = state.hlc.lock().map_err(|_| DatabaseError::MutexPoisoned {
|
||||
reason: "Failed to lock HLC service".to_string(),
|
||||
})?;
|
||||
|
||||
// 1. Extension-Eintrag erstellen (oder aktualisieren falls schon vorhanden)
|
||||
let insert_ext_sql = format!(
|
||||
"INSERT OR REPLACE INTO {} (id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
TABLE_EXTENSIONS
|
||||
);
|
||||
|
||||
SqlExecutor::execute_internal_typed(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&insert_ext_sql,
|
||||
rusqlite::params![
|
||||
full_extension_id,
|
||||
extracted.manifest.name,
|
||||
extracted.manifest.version,
|
||||
extracted.manifest.author,
|
||||
extracted.manifest.entry,
|
||||
extracted.manifest.icon,
|
||||
extracted.manifest.public_key,
|
||||
extracted.manifest.signature,
|
||||
extracted.manifest.homepage,
|
||||
extracted.manifest.description,
|
||||
true, // enabled
|
||||
],
|
||||
)?;
|
||||
|
||||
// 2. Permissions speichern (oder aktualisieren falls schon vorhanden)
|
||||
let insert_perm_sql = format!(
|
||||
"INSERT OR REPLACE INTO {} (id, extension_id, resource_type, action, target, constraints, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
TABLE_EXTENSION_PERMISSIONS
|
||||
);
|
||||
|
||||
for perm in &permissions {
|
||||
use crate::database::generated::HaexExtensionPermissions;
|
||||
let db_perm: HaexExtensionPermissions = perm.into();
|
||||
|
||||
SqlExecutor::execute_internal_typed(
|
||||
&tx,
|
||||
&hlc_service,
|
||||
&insert_perm_sql,
|
||||
rusqlite::params![
|
||||
db_perm.id,
|
||||
db_perm.extension_id,
|
||||
db_perm.resource_type,
|
||||
db_perm.action,
|
||||
db_perm.target,
|
||||
db_perm.constraints,
|
||||
db_perm.status,
|
||||
],
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit().map_err(DatabaseError::from)
|
||||
})?;
|
||||
|
||||
let extension = Extension {
|
||||
id: full_extension_id.clone(),
|
||||
@ -372,16 +559,28 @@ impl ExtensionManager {
|
||||
|
||||
// Schritt 1: Alle Daten aus der Datenbank in einem Rutsch laden.
|
||||
let extensions = with_connection(&state.db, |conn| {
|
||||
let sql = "SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM haexExtensions";
|
||||
let results = SqlExecutor::select_internal(conn, sql, &[])?;
|
||||
let sql = format!(
|
||||
"SELECT id, name, version, author, entry, icon, public_key, signature, homepage, description, enabled FROM {}",
|
||||
TABLE_EXTENSIONS
|
||||
);
|
||||
eprintln!("DEBUG: SQL Query before transformation: {}", sql);
|
||||
let results = SqlExecutor::select_internal(conn, &sql, &[])?;
|
||||
eprintln!("DEBUG: Query returned {} results", results.len());
|
||||
|
||||
let mut data = Vec::new();
|
||||
for result in results {
|
||||
let full_extension_id = result["id"]
|
||||
.as_str()
|
||||
.ok_or_else(|| DatabaseError::SerializationError {
|
||||
reason: "Missing id field".to_string(),
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
let manifest = ExtensionManifest {
|
||||
id: result["id"]
|
||||
id: result["name"]
|
||||
.as_str()
|
||||
.ok_or_else(|| DatabaseError::SerializationError {
|
||||
reason: "Missing id field".to_string(),
|
||||
reason: "Missing name field".to_string(),
|
||||
})?
|
||||
.to_string(),
|
||||
name: result["name"]
|
||||
@ -411,7 +610,11 @@ impl ExtensionManager {
|
||||
.or_else(|| result["enabled"].as_i64().map(|v| v != 0))
|
||||
.unwrap_or(false);
|
||||
|
||||
data.push(ExtensionDataFromDb { manifest, enabled });
|
||||
data.push(ExtensionDataFromDb {
|
||||
full_extension_id,
|
||||
manifest,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
Ok(data)
|
||||
})?;
|
||||
@ -419,12 +622,19 @@ impl ExtensionManager {
|
||||
// Schritt 2: Die gesammelten Daten verarbeiten (Dateisystem, State-Mutationen).
|
||||
let mut loaded_extension_ids = Vec::new();
|
||||
|
||||
for extension in extensions {
|
||||
let full_extension_id = extension.manifest.full_extension_id()?;
|
||||
eprintln!("DEBUG: Found {} extensions in database", extensions.len());
|
||||
|
||||
for extension_data in extensions {
|
||||
let full_extension_id = extension_data.full_extension_id;
|
||||
eprintln!("DEBUG: Processing extension: {}", full_extension_id);
|
||||
let extension_path =
|
||||
self.get_extension_path_by_full_extension_id(app_handle, &full_extension_id)?;
|
||||
|
||||
if !extension_path.exists() || !extension_path.join("manifest.json").exists() {
|
||||
eprintln!(
|
||||
"DEBUG: Extension files missing for: {} at {:?}",
|
||||
full_extension_id, extension_path
|
||||
);
|
||||
self.missing_extensions
|
||||
.lock()
|
||||
.map_err(|e| ExtensionError::MutexPoisoned {
|
||||
@ -432,26 +642,31 @@ impl ExtensionManager {
|
||||
})?
|
||||
.push(MissingExtension {
|
||||
full_extension_id: full_extension_id.clone(),
|
||||
name: extension.manifest.name.clone(),
|
||||
version: extension.manifest.version.clone(),
|
||||
name: extension_data.manifest.name.clone(),
|
||||
version: extension_data.manifest.version.clone(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"DEBUG: Extension loaded successfully: {}",
|
||||
full_extension_id
|
||||
);
|
||||
|
||||
let extension = Extension {
|
||||
id: full_extension_id.clone(),
|
||||
name: extension.manifest.name.clone(),
|
||||
name: extension_data.manifest.name.clone(),
|
||||
source: ExtensionSource::Production {
|
||||
path: extension_path,
|
||||
version: extension.manifest.version.clone(),
|
||||
version: extension_data.manifest.version.clone(),
|
||||
},
|
||||
manifest: extension.manifest,
|
||||
enabled: extension.enabled,
|
||||
manifest: extension_data.manifest,
|
||||
enabled: extension_data.enabled,
|
||||
last_accessed: SystemTime::now(),
|
||||
};
|
||||
|
||||
loaded_extension_ids.push(full_extension_id.clone());
|
||||
self.add_production_extension(extension)?;
|
||||
loaded_extension_ids.push(full_extension_id);
|
||||
}
|
||||
|
||||
Ok(loaded_extension_ids)
|
||||
|
||||
@ -76,6 +76,18 @@ impl ExtensionManifest {
|
||||
}
|
||||
|
||||
pub fn full_extension_id(&self) -> Result<String, ExtensionError> {
|
||||
// Validate that name and version don't contain underscores
|
||||
if self.name.contains('_') {
|
||||
return Err(ExtensionError::ValidationError {
|
||||
reason: format!("Extension name cannot contain underscores: {}", self.name),
|
||||
});
|
||||
}
|
||||
if self.version.contains('_') {
|
||||
return Err(ExtensionError::ValidationError {
|
||||
reason: format!("Extension version cannot contain underscores: {}", self.version),
|
||||
});
|
||||
}
|
||||
|
||||
let key_hash = self.calculate_key_hash()?;
|
||||
Ok(format!("{}_{}_{}", key_hash, self.name, self.version))
|
||||
}
|
||||
@ -175,6 +187,7 @@ impl ExtensionPermissions {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExtensionInfoResponse {
|
||||
pub key_hash: String,
|
||||
pub name: String,
|
||||
@ -189,10 +202,15 @@ impl ExtensionInfoResponse {
|
||||
pub fn from_extension(
|
||||
extension: &crate::extension::core::types::Extension,
|
||||
) -> Result<Self, ExtensionError> {
|
||||
// Annahme: get_tauri_origin ist in deinem `types`-Modul oder woanders definiert
|
||||
use crate::extension::core::types::get_tauri_origin;
|
||||
|
||||
// In development mode, use a wildcard for localhost to match any port
|
||||
#[cfg(debug_assertions)]
|
||||
let allowed_origin = "http://localhost:3003".to_string();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let allowed_origin = get_tauri_origin();
|
||||
|
||||
let key_hash = extension.manifest.calculate_key_hash()?;
|
||||
let full_id = extension.manifest.full_extension_id()?;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@ pub enum ExtensionErrorCode {
|
||||
MutexPoisoned = 1003,
|
||||
Database = 2000,
|
||||
Filesystem = 2001,
|
||||
FilesystemWithPath = 2004,
|
||||
Http = 2002,
|
||||
Shell = 2003,
|
||||
Manifest = 3000,
|
||||
@ -60,6 +61,12 @@ pub enum ExtensionError {
|
||||
source: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("Filesystem operation failed at '{path}': {source}")]
|
||||
FilesystemWithPath {
|
||||
path: String,
|
||||
source: std::io::Error,
|
||||
},
|
||||
|
||||
#[error("HTTP request failed: {reason}")]
|
||||
Http { reason: String },
|
||||
|
||||
@ -109,6 +116,7 @@ impl ExtensionError {
|
||||
ExtensionError::PermissionDenied { .. } => ExtensionErrorCode::PermissionDenied,
|
||||
ExtensionError::Database { .. } => ExtensionErrorCode::Database,
|
||||
ExtensionError::Filesystem { .. } => ExtensionErrorCode::Filesystem,
|
||||
ExtensionError::FilesystemWithPath { .. } => ExtensionErrorCode::FilesystemWithPath,
|
||||
ExtensionError::Http { .. } => ExtensionErrorCode::Http,
|
||||
ExtensionError::Shell { .. } => ExtensionErrorCode::Shell,
|
||||
ExtensionError::ManifestError { .. } => ExtensionErrorCode::Manifest,
|
||||
@ -146,6 +154,14 @@ impl ExtensionError {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to create a filesystem error with path context
|
||||
pub fn filesystem_with_path<P: Into<String>>(path: P, source: std::io::Error) -> Self {
|
||||
Self::FilesystemWithPath {
|
||||
path: path.into(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for ExtensionError {
|
||||
|
||||
@ -28,7 +28,29 @@ pub fn get_extension_info(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_all_extensions(state: State<AppState>) -> Result<Vec<ExtensionInfoResponse>, String> {
|
||||
pub async fn get_all_extensions(
|
||||
app_handle: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<ExtensionInfoResponse>, String> {
|
||||
// Check if extensions are loaded, if not load them first
|
||||
let needs_loading = {
|
||||
let prod_exts = state
|
||||
.extension_manager
|
||||
.production_extensions
|
||||
.lock()
|
||||
.unwrap();
|
||||
let dev_exts = state.extension_manager.dev_extensions.lock().unwrap();
|
||||
prod_exts.is_empty() && dev_exts.is_empty()
|
||||
};
|
||||
|
||||
if needs_loading {
|
||||
state
|
||||
.extension_manager
|
||||
.load_installed_extensions(&app_handle, &state)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to load extensions: {:?}", e))?;
|
||||
}
|
||||
|
||||
let mut extensions = Vec::new();
|
||||
|
||||
// Production Extensions
|
||||
@ -57,18 +79,18 @@ pub fn get_all_extensions(state: State<AppState>) -> Result<Vec<ExtensionInfoRes
|
||||
#[tauri::command]
|
||||
pub async fn preview_extension(
|
||||
state: State<'_, AppState>,
|
||||
extension_path: String,
|
||||
file_bytes: Vec<u8>,
|
||||
) -> Result<ExtensionPreview, ExtensionError> {
|
||||
state
|
||||
.extension_manager
|
||||
.preview_extension_internal(extension_path)
|
||||
.preview_extension_internal(file_bytes)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_extension_with_permissions(
|
||||
app_handle: AppHandle,
|
||||
source_path: String,
|
||||
file_bytes: Vec<u8>,
|
||||
custom_permissions: EditablePermissions,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<String, ExtensionError> {
|
||||
@ -76,7 +98,7 @@ pub async fn install_extension_with_permissions(
|
||||
.extension_manager
|
||||
.install_extension_with_permissions_internal(
|
||||
app_handle,
|
||||
source_path,
|
||||
file_bytes,
|
||||
custom_permissions,
|
||||
&state,
|
||||
)
|
||||
@ -177,6 +199,18 @@ pub async fn remove_extension(
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_extension_by_full_id(
|
||||
app_handle: AppHandle,
|
||||
full_extension_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ExtensionError> {
|
||||
state
|
||||
.extension_manager
|
||||
.remove_extension_by_full_id(&app_handle, &full_extension_id, &state)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn is_extension_installed(
|
||||
extension_id: String,
|
||||
|
||||
@ -7,7 +7,7 @@ use ts_rs::TS;
|
||||
|
||||
/// Definiert Aktionen, die auf eine Datenbank angewendet werden können.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub enum DbAction {
|
||||
Read,
|
||||
@ -38,9 +38,10 @@ impl FromStr for DbAction {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"read" => Ok(DbAction::Read),
|
||||
"read_write" => Ok(DbAction::ReadWrite),
|
||||
"readwrite" | "read_write" => Ok(DbAction::ReadWrite),
|
||||
"create" => Ok(DbAction::Create),
|
||||
"delete" => Ok(DbAction::Delete),
|
||||
"alterdrop" | "alter_drop" => Ok(DbAction::AlterDrop),
|
||||
_ => Err(ExtensionError::InvalidActionString {
|
||||
input: s.to_string(),
|
||||
resource_type: "database".to_string(),
|
||||
@ -51,7 +52,7 @@ impl FromStr for DbAction {
|
||||
|
||||
/// Definiert Aktionen, die auf das Dateisystem angewendet werden können.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub enum FsAction {
|
||||
Read,
|
||||
@ -76,7 +77,7 @@ impl FromStr for FsAction {
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"read" => Ok(FsAction::Read),
|
||||
"read_write" => Ok(FsAction::ReadWrite),
|
||||
"readwrite" | "read_write" => Ok(FsAction::ReadWrite),
|
||||
_ => Err(ExtensionError::InvalidActionString {
|
||||
input: s.to_string(),
|
||||
resource_type: "filesystem".to_string(),
|
||||
|
||||
@ -81,6 +81,7 @@ pub fn run() {
|
||||
extension::is_extension_installed,
|
||||
extension::preview_extension,
|
||||
extension::remove_extension,
|
||||
extension::remove_extension_by_full_id,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
"beforeBuildCommand": "pnpm generate",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
@ -19,25 +20,40 @@
|
||||
],
|
||||
"security": {
|
||||
"csp": {
|
||||
"default-src": ["'self'", "http://tauri.localhost"],
|
||||
"default-src": ["'self'", "http://tauri.localhost", "haex-extension:"],
|
||||
"script-src": [
|
||||
"'self'",
|
||||
"http://tauri.localhost",
|
||||
"haex-extension:",
|
||||
"'wasm-unsafe-eval'"
|
||||
],
|
||||
"style-src": ["'self'", "http://tauri.localhost", "'unsafe-inline'"],
|
||||
"style-src": [
|
||||
"'self'",
|
||||
"http://tauri.localhost",
|
||||
"haex-extension:",
|
||||
"'unsafe-inline'"
|
||||
],
|
||||
"connect-src": [
|
||||
"'self'",
|
||||
"http://tauri.localhost",
|
||||
"haex-extension:",
|
||||
"ipc:",
|
||||
"http://ipc.localhost"
|
||||
"http://ipc.localhost",
|
||||
"ws://localhost:*"
|
||||
],
|
||||
"img-src": ["'self'", "http://tauri.localhost", "data:", "blob:"],
|
||||
"font-src": ["'self'", "http://tauri.localhost"],
|
||||
"img-src": [
|
||||
"'self'",
|
||||
"http://tauri.localhost",
|
||||
"haex-extension:",
|
||||
"data:",
|
||||
"blob:"
|
||||
],
|
||||
"font-src": ["'self'", "http://tauri.localhost", "haex-extension:"],
|
||||
"object-src": ["'none'"],
|
||||
"media-src": ["'self'", "http://tauri.localhost"],
|
||||
"frame-src": ["'none'"],
|
||||
"frame-ancestors": ["'none'"]
|
||||
"media-src": ["'self'", "http://tauri.localhost", "haex-extension:"],
|
||||
"frame-src": ["haex-extension:"],
|
||||
"frame-ancestors": ["'none'"],
|
||||
"base-uri": ["'self'"]
|
||||
},
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
|
||||
Reference in New Issue
Block a user