polyfill for spa added. works now on android

This commit is contained in:
2025-10-09 11:16:25 +02:00
parent c8c3a5c73f
commit fa3348a5ad
35 changed files with 2566 additions and 373 deletions

1
src-tauri/Cargo.lock generated
View File

@ -1696,6 +1696,7 @@ dependencies = [
"ed25519-dalek",
"fs_extra",
"hex",
"lazy_static",
"mime",
"mime_guess",
"rusqlite",

View File

@ -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"] }

View File

@ -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";

View File

@ -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, };

View File

@ -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.

View File

@ -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",

View File

@ -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",

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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,

View File

@ -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(),

View File

@ -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");

View File

@ -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,