mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
cleanup. renamed postMessgages
This commit is contained in:
@ -695,9 +695,10 @@ impl ExtensionManager {
|
|||||||
&extension_data.manifest.version,
|
&extension_data.manifest.version,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if !extension_path.exists() || !extension_path.join("manifest.json").exists() {
|
// Check if extension directory exists
|
||||||
|
if !extension_path.exists() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"DEBUG: Extension files missing for: {} at {:?}",
|
"DEBUG: Extension directory missing for: {} at {:?}",
|
||||||
extension_id, extension_path
|
extension_id, extension_path
|
||||||
);
|
);
|
||||||
self.missing_extensions
|
self.missing_extensions
|
||||||
@ -714,6 +715,71 @@ impl ExtensionManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read haextension_dir from config if it exists, otherwise use default
|
||||||
|
let config_path = extension_path.join("haextension.config.json");
|
||||||
|
let haextension_dir = if config_path.exists() {
|
||||||
|
match std::fs::read_to_string(&config_path) {
|
||||||
|
Ok(config_content) => {
|
||||||
|
match serde_json::from_str::<serde_json::Value>(&config_content) {
|
||||||
|
Ok(config) => {
|
||||||
|
let dir = config
|
||||||
|
.get("dev")
|
||||||
|
.and_then(|dev| dev.get("haextension_dir"))
|
||||||
|
.and_then(|dir| dir.as_str())
|
||||||
|
.unwrap_or("haextension")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Security: Validate that haextension_dir doesn't contain ".."
|
||||||
|
if dir.contains("..") {
|
||||||
|
eprintln!(
|
||||||
|
"DEBUG: Invalid haextension_dir for: {}, contains '..'",
|
||||||
|
extension_id
|
||||||
|
);
|
||||||
|
self.missing_extensions
|
||||||
|
.lock()
|
||||||
|
.map_err(|e| ExtensionError::MutexPoisoned {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})?
|
||||||
|
.push(MissingExtension {
|
||||||
|
id: extension_id.clone(),
|
||||||
|
public_key: extension_data.manifest.public_key.clone(),
|
||||||
|
name: extension_data.manifest.name.clone(),
|
||||||
|
version: extension_data.manifest.version.clone(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
Err(_) => "haextension".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => "haextension".to_string(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"haextension".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if manifest.json exists in the haextension_dir
|
||||||
|
let manifest_path = extension_path.join(&haextension_dir).join("manifest.json");
|
||||||
|
if !manifest_path.exists() {
|
||||||
|
eprintln!(
|
||||||
|
"DEBUG: manifest.json missing for: {} at {:?}",
|
||||||
|
extension_id, manifest_path
|
||||||
|
);
|
||||||
|
self.missing_extensions
|
||||||
|
.lock()
|
||||||
|
.map_err(|e| ExtensionError::MutexPoisoned {
|
||||||
|
reason: e.to_string(),
|
||||||
|
})?
|
||||||
|
.push(MissingExtension {
|
||||||
|
id: extension_id.clone(),
|
||||||
|
public_key: extension_data.manifest.public_key.clone(),
|
||||||
|
name: extension_data.manifest.name.clone(),
|
||||||
|
version: extension_data.manifest.version.clone(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
eprintln!("DEBUG: Extension loaded successfully: {}", extension_id);
|
eprintln!("DEBUG: Extension loaded successfully: {}", extension_id);
|
||||||
|
|
||||||
let extension = Extension {
|
let extension = Extension {
|
||||||
|
|||||||
@ -37,7 +37,7 @@ pub async fn get_all_extensions(
|
|||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Vec<ExtensionInfoResponse>, String> {
|
) -> Result<Vec<ExtensionInfoResponse>, String> {
|
||||||
// Check if extensions are loaded, if not load them first
|
// Check if extensions are loaded, if not load them first
|
||||||
let needs_loading = {
|
/* let needs_loading = {
|
||||||
let prod_exts = state
|
let prod_exts = state
|
||||||
.extension_manager
|
.extension_manager
|
||||||
.production_extensions
|
.production_extensions
|
||||||
@ -45,15 +45,15 @@ pub async fn get_all_extensions(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let dev_exts = state.extension_manager.dev_extensions.lock().unwrap();
|
let dev_exts = state.extension_manager.dev_extensions.lock().unwrap();
|
||||||
prod_exts.is_empty() && dev_exts.is_empty()
|
prod_exts.is_empty() && dev_exts.is_empty()
|
||||||
};
|
}; */
|
||||||
|
|
||||||
if needs_loading {
|
/* if needs_loading { */
|
||||||
state
|
state
|
||||||
.extension_manager
|
.extension_manager
|
||||||
.load_installed_extensions(&app_handle, &state)
|
.load_installed_extensions(&app_handle, &state)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to load extensions: {:?}", e))?;
|
.map_err(|e| format!("Failed to load extensions: {:?}", e))?;
|
||||||
}
|
/* } */
|
||||||
|
|
||||||
let mut extensions = Vec::new();
|
let mut extensions = Vec::new();
|
||||||
|
|
||||||
@ -193,13 +193,7 @@ pub async fn remove_extension(
|
|||||||
) -> Result<(), ExtensionError> {
|
) -> Result<(), ExtensionError> {
|
||||||
state
|
state
|
||||||
.extension_manager
|
.extension_manager
|
||||||
.remove_extension_internal(
|
.remove_extension_internal(&app_handle, &public_key, &name, &version, &state)
|
||||||
&app_handle,
|
|
||||||
&public_key,
|
|
||||||
&name,
|
|
||||||
&version,
|
|
||||||
&state,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +253,8 @@ fn default_haextension_dir() -> String {
|
|||||||
|
|
||||||
/// Check if a dev server is reachable by making a simple HTTP request
|
/// Check if a dev server is reachable by making a simple HTTP request
|
||||||
async fn check_dev_server_health(url: &str) -> bool {
|
async fn check_dev_server_health(url: &str) -> bool {
|
||||||
use tauri_plugin_http::reqwest;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use tauri_plugin_http::reqwest;
|
||||||
|
|
||||||
// Try to connect with a short timeout
|
// Try to connect with a short timeout
|
||||||
let client = reqwest::Client::builder()
|
let client = reqwest::Client::builder()
|
||||||
@ -295,16 +289,14 @@ pub async fn load_dev_extension(
|
|||||||
// 1. Read haextension.config.json to get dev server config and haextension directory
|
// 1. Read haextension.config.json to get dev server config and haextension directory
|
||||||
let config_path = extension_path_buf.join("haextension.config.json");
|
let config_path = extension_path_buf.join("haextension.config.json");
|
||||||
let (host, port, haextension_dir) = if config_path.exists() {
|
let (host, port, haextension_dir) = if config_path.exists() {
|
||||||
let config_content = std::fs::read_to_string(&config_path).map_err(|e| {
|
let config_content =
|
||||||
ExtensionError::ValidationError {
|
std::fs::read_to_string(&config_path).map_err(|e| ExtensionError::ValidationError {
|
||||||
reason: format!("Failed to read haextension.config.json: {}", e),
|
reason: format!("Failed to read haextension.config.json: {}", e),
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let config: HaextensionConfig = serde_json::from_str(&config_content).map_err(|e| {
|
let config: HaextensionConfig =
|
||||||
ExtensionError::ValidationError {
|
serde_json::from_str(&config_content).map_err(|e| ExtensionError::ValidationError {
|
||||||
reason: format!("Failed to parse haextension.config.json: {}", e),
|
reason: format!("Failed to parse haextension.config.json: {}", e),
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
(config.dev.host, config.dev.port, config.dev.haextension_dir)
|
(config.dev.host, config.dev.port, config.dev.haextension_dir)
|
||||||
@ -329,7 +321,9 @@ pub async fn load_dev_extension(
|
|||||||
eprintln!("✅ Dev server is reachable");
|
eprintln!("✅ Dev server is reachable");
|
||||||
|
|
||||||
// 2. Build path to manifest: <extension_path>/<haextension_dir>/manifest.json
|
// 2. Build path to manifest: <extension_path>/<haextension_dir>/manifest.json
|
||||||
let manifest_path = extension_path_buf.join(&haextension_dir).join("manifest.json");
|
let manifest_path = extension_path_buf
|
||||||
|
.join(&haextension_dir)
|
||||||
|
.join("manifest.json");
|
||||||
|
|
||||||
// Check if manifest exists
|
// Check if manifest exists
|
||||||
if !manifest_path.exists() {
|
if !manifest_path.exists() {
|
||||||
@ -342,20 +336,15 @@ pub async fn load_dev_extension(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Read and parse manifest
|
// 3. Read and parse manifest
|
||||||
let manifest_content = std::fs::read_to_string(&manifest_path).map_err(|e| {
|
let manifest_content =
|
||||||
ExtensionError::ManifestError {
|
std::fs::read_to_string(&manifest_path).map_err(|e| ExtensionError::ManifestError {
|
||||||
reason: format!("Failed to read manifest: {}", e),
|
reason: format!("Failed to read manifest: {}", e),
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
let manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
||||||
|
|
||||||
// 4. Generate a unique ID for dev extension: dev_<public_key_first_8>_<name>
|
// 4. Generate a unique ID for dev extension: dev_<public_key_first_8>_<name>
|
||||||
let key_prefix = manifest
|
let key_prefix = manifest.public_key.chars().take(8).collect::<String>();
|
||||||
.public_key
|
|
||||||
.chars()
|
|
||||||
.take(8)
|
|
||||||
.collect::<String>();
|
|
||||||
let extension_id = format!("dev_{}_{}", key_prefix, manifest.name);
|
let extension_id = format!("dev_{}_{}", key_prefix, manifest.name);
|
||||||
|
|
||||||
// 5. Check if dev extension already exists (allow reload)
|
// 5. Check if dev extension already exists (allow reload)
|
||||||
@ -404,12 +393,10 @@ pub fn remove_dev_extension(
|
|||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), ExtensionError> {
|
) -> Result<(), ExtensionError> {
|
||||||
// Only remove from dev_extensions, not production_extensions
|
// Only remove from dev_extensions, not production_extensions
|
||||||
let mut dev_exts = state
|
let mut dev_exts = state.extension_manager.dev_extensions.lock().map_err(|e| {
|
||||||
.extension_manager
|
ExtensionError::MutexPoisoned {
|
||||||
.dev_extensions
|
|
||||||
.lock()
|
|
||||||
.map_err(|e| ExtensionError::MutexPoisoned {
|
|
||||||
reason: e.to_string(),
|
reason: e.to_string(),
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Find and remove by public_key and name
|
// Find and remove by public_key and name
|
||||||
@ -423,10 +410,7 @@ pub fn remove_dev_extension(
|
|||||||
eprintln!("✅ Dev extension removed: {}", name);
|
eprintln!("✅ Dev extension removed: {}", name);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ExtensionError::NotFound {
|
Err(ExtensionError::NotFound { public_key, name })
|
||||||
public_key,
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -85,7 +85,10 @@
|
|||||||
>
|
>
|
||||||
<!-- Overview Mode: Teleport to window preview -->
|
<!-- Overview Mode: Teleport to window preview -->
|
||||||
<Teleport
|
<Teleport
|
||||||
v-if="windowManager.showWindowOverview && overviewWindowState.has(window.id)"
|
v-if="
|
||||||
|
windowManager.showWindowOverview &&
|
||||||
|
overviewWindowState.has(window.id)
|
||||||
|
"
|
||||||
:to="`#window-preview-${window.id}`"
|
:to="`#window-preview-${window.id}`"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -330,9 +333,9 @@ const getWorkspaceIcons = (workspaceId: string) => {
|
|||||||
.filter((item) => item.workspaceId === workspaceId)
|
.filter((item) => item.workspaceId === workspaceId)
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
if (item.itemType === 'system') {
|
if (item.itemType === 'system') {
|
||||||
const systemWindow = windowManager.getAllSystemWindows().find(
|
const systemWindow = windowManager
|
||||||
(win) => win.id === item.referenceId,
|
.getAllSystemWindows()
|
||||||
)
|
.find((win) => win.id === item.referenceId)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
@ -346,6 +349,7 @@ const getWorkspaceIcons = (workspaceId: string) => {
|
|||||||
(ext) => ext.id === item.referenceId,
|
(ext) => ext.id === item.referenceId,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log('found ext', extension)
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
label: extension?.name || 'Unknown',
|
label: extension?.name || 'Unknown',
|
||||||
@ -429,7 +433,9 @@ const handleDragOver = (event: DragEvent) => {
|
|||||||
const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
||||||
if (!event.dataTransfer) return
|
if (!event.dataTransfer) return
|
||||||
|
|
||||||
const launcherItemData = event.dataTransfer.getData('application/haex-launcher-item')
|
const launcherItemData = event.dataTransfer.getData(
|
||||||
|
'application/haex-launcher-item',
|
||||||
|
)
|
||||||
if (!launcherItemData) return
|
if (!launcherItemData) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -441,7 +447,9 @@ const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get drop position relative to desktop
|
// Get drop position relative to desktop
|
||||||
const desktopRect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
const desktopRect = (
|
||||||
|
event.currentTarget as HTMLElement
|
||||||
|
).getBoundingClientRect()
|
||||||
const x = Math.max(0, event.clientX - desktopRect.left - 32) // Center icon (64px / 2)
|
const x = Math.max(0, event.clientX - desktopRect.left - 32) // Center icon (64px / 2)
|
||||||
const y = Math.max(0, event.clientY - desktopRect.top - 32)
|
const y = Math.max(0, event.clientY - desktopRect.top - 32)
|
||||||
|
|
||||||
@ -451,7 +459,7 @@ const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
|||||||
item.id,
|
item.id,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
workspaceId
|
workspaceId,
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create desktop icon:', error)
|
console.error('Failed to create desktop icon:', error)
|
||||||
@ -611,7 +619,10 @@ const MAX_PREVIEW_HEIGHT = 450 // 50% increase from 300
|
|||||||
|
|
||||||
// Store window state for overview (position only, size stays original)
|
// Store window state for overview (position only, size stays original)
|
||||||
const overviewWindowState = ref(
|
const overviewWindowState = ref(
|
||||||
new Map<string, { x: number; y: number; width: number; height: number; scale: number }>(),
|
new Map<
|
||||||
|
string,
|
||||||
|
{ x: number; y: number; width: number; height: number; scale: number }
|
||||||
|
>(),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Calculate scale and card dimensions for each window
|
// Calculate scale and card dimensions for each window
|
||||||
@ -635,7 +646,10 @@ watch(
|
|||||||
finalScale = MIN_PREVIEW_WIDTH / window.width
|
finalScale = MIN_PREVIEW_WIDTH / window.width
|
||||||
}
|
}
|
||||||
if (scaledHeight < MIN_PREVIEW_HEIGHT) {
|
if (scaledHeight < MIN_PREVIEW_HEIGHT) {
|
||||||
finalScale = Math.max(finalScale, MIN_PREVIEW_HEIGHT / window.height)
|
finalScale = Math.max(
|
||||||
|
finalScale,
|
||||||
|
MIN_PREVIEW_HEIGHT / window.height,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
overviewWindowState.value.set(window.id, {
|
overviewWindowState.value.set(window.id, {
|
||||||
|
|||||||
@ -89,7 +89,11 @@ const removeExtensionAsync = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await extensionStore.removeExtensionAsync(extension.id, extension.version)
|
await extensionStore.removeExtensionAsync(
|
||||||
|
extension.publicKey,
|
||||||
|
extension.name,
|
||||||
|
extension.version,
|
||||||
|
)
|
||||||
await extensionStore.loadExtensionsAsync()
|
await extensionStore.loadExtensionsAsync()
|
||||||
|
|
||||||
add({
|
add({
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
// composables/extensionContextBroadcast.ts
|
|
||||||
// NOTE: This composable is deprecated. Use tabsStore.broadcastToAllTabs() instead.
|
|
||||||
// Keeping for backwards compatibility.
|
|
||||||
|
|
||||||
import { getExtensionWindow } from './extensionMessageHandler'
|
|
||||||
|
|
||||||
export const useExtensionContextBroadcast = () => {
|
|
||||||
// Globaler State für Extension IDs statt IFrames
|
|
||||||
const extensionIds = useState<Set<string>>(
|
|
||||||
'extension-ids',
|
|
||||||
() => new Set(),
|
|
||||||
)
|
|
||||||
|
|
||||||
const registerExtensionIframe = (_iframe: HTMLIFrameElement, extensionId: string) => {
|
|
||||||
extensionIds.value.add(extensionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const unregisterExtensionIframe = (_iframe: HTMLIFrameElement, extensionId: string) => {
|
|
||||||
extensionIds.value.delete(extensionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const broadcastContextChange = (context: {
|
|
||||||
theme: string
|
|
||||||
locale: string
|
|
||||||
platform: string
|
|
||||||
}) => {
|
|
||||||
const message = {
|
|
||||||
type: 'context.changed',
|
|
||||||
data: { context },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionIds.value.forEach((extensionId) => {
|
|
||||||
const win = getExtensionWindow(extensionId)
|
|
||||||
if (win) {
|
|
||||||
win.postMessage(message, '*')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const broadcastSearchRequest = (query: string, requestId: string) => {
|
|
||||||
const message = {
|
|
||||||
type: 'search.request',
|
|
||||||
data: {
|
|
||||||
query: { query, limit: 10 },
|
|
||||||
requestId,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionIds.value.forEach((extensionId) => {
|
|
||||||
const win = getExtensionWindow(extensionId)
|
|
||||||
if (win) {
|
|
||||||
win.postMessage(message, '*')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
registerExtensionIframe,
|
|
||||||
unregisterExtensionIframe,
|
|
||||||
broadcastContextChange,
|
|
||||||
broadcastSearchRequest,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -166,20 +166,18 @@ const registerGlobalMessageHandler = () => {
|
|||||||
try {
|
try {
|
||||||
let result: unknown
|
let result: unknown
|
||||||
|
|
||||||
if (request.method.startsWith('extension.')) {
|
if (request.method.startsWith('haextension.context.')) {
|
||||||
result = await handleExtensionMethodAsync(request, instance.extension)
|
|
||||||
} else if (request.method.startsWith('db.')) {
|
|
||||||
result = await handleDatabaseMethodAsync(request, instance.extension)
|
|
||||||
} else if (request.method.startsWith('fs.')) {
|
|
||||||
result = await handleFilesystemMethodAsync(request, instance.extension)
|
|
||||||
} else if (request.method.startsWith('http.')) {
|
|
||||||
result = await handleHttpMethodAsync(request, instance.extension)
|
|
||||||
} else if (request.method.startsWith('permissions.')) {
|
|
||||||
result = await handlePermissionsMethodAsync(request, instance.extension)
|
|
||||||
} else if (request.method.startsWith('context.')) {
|
|
||||||
result = await handleContextMethodAsync(request)
|
result = await handleContextMethodAsync(request)
|
||||||
} else if (request.method.startsWith('storage.')) {
|
} else if (request.method.startsWith('haextension.storage.')) {
|
||||||
result = await handleStorageMethodAsync(request, instance)
|
result = await handleStorageMethodAsync(request, instance)
|
||||||
|
} else if (request.method.startsWith('haextension.db.')) {
|
||||||
|
result = await handleDatabaseMethodAsync(request, instance.extension)
|
||||||
|
} else if (request.method.startsWith('haextension.fs.')) {
|
||||||
|
result = await handleFilesystemMethodAsync(request, instance.extension)
|
||||||
|
} else if (request.method.startsWith('haextension.http.')) {
|
||||||
|
result = await handleHttpMethodAsync(request, instance.extension)
|
||||||
|
} else if (request.method.startsWith('haextension.permissions.')) {
|
||||||
|
result = await handlePermissionsMethodAsync(request, instance.extension)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown method: ${request.method}`)
|
throw new Error(`Unknown method: ${request.method}`)
|
||||||
}
|
}
|
||||||
@ -328,31 +326,28 @@ export const getExtensionWindow = (extensionId: string): Window | undefined => {
|
|||||||
return getAllInstanceWindows(extensionId)[0]
|
return getAllInstanceWindows(extensionId)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// Broadcast context changes to all extension instances
|
||||||
// Extension Methods
|
export const broadcastContextToAllExtensions = (context: {
|
||||||
// ==========================================
|
theme: string
|
||||||
|
locale: string
|
||||||
|
platform?: string
|
||||||
|
}) => {
|
||||||
|
const message = {
|
||||||
|
type: 'haextension.context.changed',
|
||||||
|
data: { context },
|
||||||
|
timestamp: Date.now(),
|
||||||
|
}
|
||||||
|
|
||||||
async function handleExtensionMethodAsync(
|
console.log('[ExtensionHandler] Broadcasting context to all extensions:', context)
|
||||||
request: ExtensionRequest,
|
|
||||||
extension: IHaexHubExtension, // Direkter Typ, kein ComputedRef mehr
|
// Send to all registered extension windows
|
||||||
) {
|
for (const [_, instance] of iframeRegistry.entries()) {
|
||||||
switch (request.method) {
|
const win = windowIdToWindowMap.get(instance.windowId)
|
||||||
case 'extension.getInfo': {
|
if (win) {
|
||||||
const info = (await invoke('get_extension_info', {
|
console.log('[ExtensionHandler] Sending context to:', instance.extension.name, instance.windowId)
|
||||||
publicKey: extension.publicKey,
|
win.postMessage(message, '*')
|
||||||
name: extension.name,
|
|
||||||
})) as Record<string, unknown>
|
|
||||||
// Override allowedOrigin with the actual window origin
|
|
||||||
// This fixes the dev-mode issue where Rust returns "tauri://localhost"
|
|
||||||
// but the actual origin is "http://localhost:3003"
|
|
||||||
return {
|
|
||||||
...info,
|
|
||||||
allowedOrigin: window.location.origin,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
throw new Error(`Unknown extension method: ${request.method}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@ -369,7 +364,7 @@ async function handleDatabaseMethodAsync(
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'db.query': {
|
case 'haextension.db.query': {
|
||||||
const rows = await invoke<unknown[]>('extension_sql_select', {
|
const rows = await invoke<unknown[]>('extension_sql_select', {
|
||||||
sql: params.query || '',
|
sql: params.query || '',
|
||||||
params: params.params || [],
|
params: params.params || [],
|
||||||
@ -383,7 +378,7 @@ async function handleDatabaseMethodAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'db.execute': {
|
case 'haextension.db.execute': {
|
||||||
await invoke<string[]>('extension_sql_execute', {
|
await invoke<string[]>('extension_sql_execute', {
|
||||||
sql: params.query || '',
|
sql: params.query || '',
|
||||||
params: params.params || [],
|
params: params.params || [],
|
||||||
@ -397,7 +392,7 @@ async function handleDatabaseMethodAsync(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'db.transaction': {
|
case 'haextension.db.transaction': {
|
||||||
const statements =
|
const statements =
|
||||||
(request.params as { statements?: string[] }).statements || []
|
(request.params as { statements?: string[] }).statements || []
|
||||||
|
|
||||||
@ -467,7 +462,7 @@ async function handlePermissionsMethodAsync(
|
|||||||
|
|
||||||
async function handleContextMethodAsync(request: ExtensionRequest) {
|
async function handleContextMethodAsync(request: ExtensionRequest) {
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'context.get':
|
case 'haextension.context.get':
|
||||||
if (!contextGetters) {
|
if (!contextGetters) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Context not initialized. Make sure useExtensionMessageHandler is called in a component.',
|
'Context not initialized. Make sure useExtensionMessageHandler is called in a component.',
|
||||||
@ -499,25 +494,25 @@ async function handleStorageMethodAsync(
|
|||||||
)
|
)
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'storage.getItem': {
|
case 'haextension.storage.getItem': {
|
||||||
const key = request.params.key as string
|
const key = request.params.key as string
|
||||||
return localStorage.getItem(storageKey + key)
|
return localStorage.getItem(storageKey + key)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'storage.setItem': {
|
case 'haextension.storage.setItem': {
|
||||||
const key = request.params.key as string
|
const key = request.params.key as string
|
||||||
const value = request.params.value as string
|
const value = request.params.value as string
|
||||||
localStorage.setItem(storageKey + key, value)
|
localStorage.setItem(storageKey + key, value)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'storage.removeItem': {
|
case 'haextension.storage.removeItem': {
|
||||||
const key = request.params.key as string
|
const key = request.params.key as string
|
||||||
localStorage.removeItem(storageKey + key)
|
localStorage.removeItem(storageKey + key)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'storage.clear': {
|
case 'haextension.storage.clear': {
|
||||||
// Remove only instance-specific keys
|
// Remove only instance-specific keys
|
||||||
const keys = Object.keys(localStorage).filter((k) =>
|
const keys = Object.keys(localStorage).filter((k) =>
|
||||||
k.startsWith(storageKey),
|
k.startsWith(storageKey),
|
||||||
@ -526,7 +521,7 @@ async function handleStorageMethodAsync(
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'storage.keys': {
|
case 'haextension.storage.keys': {
|
||||||
// Return only instance-specific keys (without prefix)
|
// Return only instance-specific keys (without prefix)
|
||||||
const keys = Object.keys(localStorage)
|
const keys = Object.keys(localStorage)
|
||||||
.filter((k) => k.startsWith(storageKey))
|
.filter((k) => k.startsWith(storageKey))
|
||||||
|
|||||||
@ -90,6 +90,7 @@ export const useExtensionsStore = defineStore('extensionsStore', () => {
|
|||||||
const extensions =
|
const extensions =
|
||||||
await invoke<ExtensionInfoResponse[]>('get_all_extensions')
|
await invoke<ExtensionInfoResponse[]>('get_all_extensions')
|
||||||
|
|
||||||
|
console.log('get_all_extensions', extensions)
|
||||||
// ExtensionInfoResponse is now directly compatible with IHaexHubExtension
|
// ExtensionInfoResponse is now directly compatible with IHaexHubExtension
|
||||||
availableExtensions.value = extensions
|
availableExtensions.value = extensions
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -1,175 +0,0 @@
|
|||||||
// stores/extensions/tabs.ts
|
|
||||||
import type { IHaexHubExtension } from '~/types/haexhub'
|
|
||||||
import { getExtensionWindow } from '~/composables/extensionMessageHandler'
|
|
||||||
|
|
||||||
interface ExtensionTab {
|
|
||||||
extension: IHaexHubExtension
|
|
||||||
iframe: HTMLIFrameElement | null
|
|
||||||
isVisible: boolean
|
|
||||||
lastAccessed: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useExtensionTabsStore = defineStore('extensionTabsStore', () => {
|
|
||||||
// State
|
|
||||||
const openTabs = ref(new Map<string, ExtensionTab>())
|
|
||||||
const activeTabId = ref<string | null>(null)
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
const activeTab = computed(() => {
|
|
||||||
if (!activeTabId.value) return null
|
|
||||||
return openTabs.value.get(activeTabId.value) || null
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabCount = computed(() => openTabs.value.size)
|
|
||||||
|
|
||||||
const sortedTabs = computed(() => {
|
|
||||||
return Array.from(openTabs.value.values()).sort(
|
|
||||||
(a, b) => b.lastAccessed - a.lastAccessed,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const extensionsStore = useExtensionsStore()
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
const openTab = (extensionId: string) => {
|
|
||||||
const extension = extensionsStore.availableExtensions.find(
|
|
||||||
(ext) => ext.id === extensionId,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!extension) {
|
|
||||||
console.error(`Extension ${extensionId} nicht gefunden`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if extension is enabled
|
|
||||||
if (!extension.enabled) {
|
|
||||||
console.warn(
|
|
||||||
`Extension ${extensionId} ist deaktiviert und kann nicht geöffnet werden`,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bereits geöffnet? Nur aktivieren
|
|
||||||
if (openTabs.value.has(extensionId)) {
|
|
||||||
setActiveTab(extensionId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limit: Max 10 Tabs
|
|
||||||
if (openTabs.value.size >= 10) {
|
|
||||||
const oldestInactive = sortedTabs.value
|
|
||||||
.filter((tab) => tab.extension.id !== activeTabId.value)
|
|
||||||
.pop()
|
|
||||||
|
|
||||||
if (oldestInactive) {
|
|
||||||
closeTab(oldestInactive.extension.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Neuen Tab erstellen
|
|
||||||
openTabs.value.set(extensionId, {
|
|
||||||
extension,
|
|
||||||
iframe: null,
|
|
||||||
isVisible: false,
|
|
||||||
lastAccessed: Date.now(),
|
|
||||||
})
|
|
||||||
|
|
||||||
setActiveTab(extensionId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setActiveTab = (extensionId: string) => {
|
|
||||||
// Verstecke aktuellen Tab
|
|
||||||
if (activeTabId.value && openTabs.value.has(activeTabId.value)) {
|
|
||||||
const currentTab = openTabs.value.get(activeTabId.value)!
|
|
||||||
currentTab.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zeige neuen Tab
|
|
||||||
const newTab = openTabs.value.get(extensionId)
|
|
||||||
if (newTab) {
|
|
||||||
const now = Date.now()
|
|
||||||
const inactiveDuration = now - newTab.lastAccessed
|
|
||||||
const TEN_MINUTES = 10 * 60 * 1000
|
|
||||||
|
|
||||||
// Reload iframe if inactive for more than 10 minutes
|
|
||||||
if (inactiveDuration > TEN_MINUTES && newTab.iframe) {
|
|
||||||
console.log(
|
|
||||||
`[TabStore] Reloading extension ${extensionId} after ${Math.round(inactiveDuration / 1000)}s inactivity`,
|
|
||||||
)
|
|
||||||
const currentSrc = newTab.iframe.src
|
|
||||||
newTab.iframe.src = 'about:blank'
|
|
||||||
// Small delay to ensure reload
|
|
||||||
setTimeout(() => {
|
|
||||||
if (newTab.iframe) {
|
|
||||||
newTab.iframe.src = currentSrc
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
newTab.isVisible = true
|
|
||||||
newTab.lastAccessed = now
|
|
||||||
activeTabId.value = extensionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeTab = (extensionId: string) => {
|
|
||||||
const tab = openTabs.value.get(extensionId)
|
|
||||||
if (!tab) return
|
|
||||||
|
|
||||||
// IFrame entfernen
|
|
||||||
tab.iframe?.remove()
|
|
||||||
openTabs.value.delete(extensionId)
|
|
||||||
|
|
||||||
// Nächsten Tab aktivieren
|
|
||||||
if (activeTabId.value === extensionId) {
|
|
||||||
const remaining = sortedTabs.value
|
|
||||||
const nextTab = remaining[0]
|
|
||||||
|
|
||||||
if (nextTab) {
|
|
||||||
setActiveTab(nextTab.extension.id)
|
|
||||||
} else {
|
|
||||||
activeTabId.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerIFrame = (extensionId: string, iframe: HTMLIFrameElement) => {
|
|
||||||
const tab = openTabs.value.get(extensionId)
|
|
||||||
if (tab) {
|
|
||||||
tab.iframe = iframe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const broadcastToAllTabs = (message: unknown) => {
|
|
||||||
openTabs.value.forEach(({ extension }) => {
|
|
||||||
// Use sandbox-compatible window reference
|
|
||||||
const win = getExtensionWindow(extension.id)
|
|
||||||
if (win) {
|
|
||||||
win.postMessage(message, '*')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeAllTabs = () => {
|
|
||||||
openTabs.value.forEach((tab) => tab.iframe?.remove())
|
|
||||||
openTabs.value.clear()
|
|
||||||
activeTabId.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// State
|
|
||||||
openTabs,
|
|
||||||
activeTabId,
|
|
||||||
// Getters
|
|
||||||
activeTab,
|
|
||||||
tabCount,
|
|
||||||
sortedTabs,
|
|
||||||
// Actions
|
|
||||||
openTab,
|
|
||||||
setActiveTab,
|
|
||||||
closeTab,
|
|
||||||
registerIFrame,
|
|
||||||
broadcastToAllTabs,
|
|
||||||
closeAllTabs,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { breakpointsTailwind } from '@vueuse/core'
|
import { breakpointsTailwind } from '@vueuse/core'
|
||||||
|
import { broadcastContextToAllExtensions } from '~/composables/extensionMessageHandler'
|
||||||
import de from './de.json'
|
import de from './de.json'
|
||||||
import en from './en.json'
|
import en from './en.json'
|
||||||
|
|
||||||
@ -9,6 +10,8 @@ export const useUiStore = defineStore('uiStore', () => {
|
|||||||
const isSmallScreen = breakpoints.smaller('sm')
|
const isSmallScreen = breakpoints.smaller('sm')
|
||||||
|
|
||||||
const { $i18n } = useNuxtApp()
|
const { $i18n } = useNuxtApp()
|
||||||
|
const { locale } = useI18n()
|
||||||
|
const { platform } = useDeviceStore()
|
||||||
|
|
||||||
$i18n.setLocaleMessage('de', {
|
$i18n.setLocaleMessage('de', {
|
||||||
ui: de,
|
ui: de,
|
||||||
@ -56,6 +59,15 @@ export const useUiStore = defineStore('uiStore', () => {
|
|||||||
colorMode.preference = currentThemeName.value
|
colorMode.preference = currentThemeName.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Broadcast theme and locale changes to extensions
|
||||||
|
watch([currentThemeName, locale], () => {
|
||||||
|
broadcastContextToAllExtensions({
|
||||||
|
theme: currentThemeName.value,
|
||||||
|
locale: locale.value,
|
||||||
|
platform,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const viewportHeightWithoutHeader = ref(0)
|
const viewportHeightWithoutHeader = ref(0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user