mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 14:10:52 +01:00
cleanup. renamed postMessgages
This commit is contained in:
@ -695,9 +695,10 @@ impl ExtensionManager {
|
||||
&extension_data.manifest.version,
|
||||
)?;
|
||||
|
||||
if !extension_path.exists() || !extension_path.join("manifest.json").exists() {
|
||||
// Check if extension directory exists
|
||||
if !extension_path.exists() {
|
||||
eprintln!(
|
||||
"DEBUG: Extension files missing for: {} at {:?}",
|
||||
"DEBUG: Extension directory missing for: {} at {:?}",
|
||||
extension_id, extension_path
|
||||
);
|
||||
self.missing_extensions
|
||||
@ -714,6 +715,71 @@ impl ExtensionManager {
|
||||
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);
|
||||
|
||||
let extension = Extension {
|
||||
|
||||
@ -37,7 +37,7 @@ pub async fn get_all_extensions(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<ExtensionInfoResponse>, String> {
|
||||
// Check if extensions are loaded, if not load them first
|
||||
let needs_loading = {
|
||||
/* let needs_loading = {
|
||||
let prod_exts = state
|
||||
.extension_manager
|
||||
.production_extensions
|
||||
@ -45,15 +45,15 @@ pub async fn get_all_extensions(
|
||||
.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))?;
|
||||
}
|
||||
/* 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();
|
||||
|
||||
@ -193,13 +193,7 @@ pub async fn remove_extension(
|
||||
) -> Result<(), ExtensionError> {
|
||||
state
|
||||
.extension_manager
|
||||
.remove_extension_internal(
|
||||
&app_handle,
|
||||
&public_key,
|
||||
&name,
|
||||
&version,
|
||||
&state,
|
||||
)
|
||||
.remove_extension_internal(&app_handle, &public_key, &name, &version, &state)
|
||||
.await
|
||||
}
|
||||
|
||||
@ -259,8 +253,8 @@ fn default_haextension_dir() -> String {
|
||||
|
||||
/// Check if a dev server is reachable by making a simple HTTP request
|
||||
async fn check_dev_server_health(url: &str) -> bool {
|
||||
use tauri_plugin_http::reqwest;
|
||||
use std::time::Duration;
|
||||
use tauri_plugin_http::reqwest;
|
||||
|
||||
// Try to connect with a short timeout
|
||||
let client = reqwest::Client::builder()
|
||||
@ -295,17 +289,15 @@ pub async fn load_dev_extension(
|
||||
// 1. Read haextension.config.json to get dev server config and haextension directory
|
||||
let config_path = extension_path_buf.join("haextension.config.json");
|
||||
let (host, port, haextension_dir) = if config_path.exists() {
|
||||
let config_content = std::fs::read_to_string(&config_path).map_err(|e| {
|
||||
ExtensionError::ValidationError {
|
||||
let config_content =
|
||||
std::fs::read_to_string(&config_path).map_err(|e| ExtensionError::ValidationError {
|
||||
reason: format!("Failed to read haextension.config.json: {}", e),
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
let config: HaextensionConfig = serde_json::from_str(&config_content).map_err(|e| {
|
||||
ExtensionError::ValidationError {
|
||||
let config: HaextensionConfig =
|
||||
serde_json::from_str(&config_content).map_err(|e| ExtensionError::ValidationError {
|
||||
reason: format!("Failed to parse haextension.config.json: {}", e),
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
(config.dev.host, config.dev.port, config.dev.haextension_dir)
|
||||
} else {
|
||||
@ -329,7 +321,9 @@ pub async fn load_dev_extension(
|
||||
eprintln!("✅ Dev server is reachable");
|
||||
|
||||
// 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
|
||||
if !manifest_path.exists() {
|
||||
@ -342,20 +336,15 @@ pub async fn load_dev_extension(
|
||||
}
|
||||
|
||||
// 3. Read and parse manifest
|
||||
let manifest_content = std::fs::read_to_string(&manifest_path).map_err(|e| {
|
||||
ExtensionError::ManifestError {
|
||||
let manifest_content =
|
||||
std::fs::read_to_string(&manifest_path).map_err(|e| ExtensionError::ManifestError {
|
||||
reason: format!("Failed to read manifest: {}", e),
|
||||
}
|
||||
})?;
|
||||
})?;
|
||||
|
||||
let manifest: ExtensionManifest = serde_json::from_str(&manifest_content)?;
|
||||
|
||||
// 4. Generate a unique ID for dev extension: dev_<public_key_first_8>_<name>
|
||||
let key_prefix = manifest
|
||||
.public_key
|
||||
.chars()
|
||||
.take(8)
|
||||
.collect::<String>();
|
||||
let key_prefix = manifest.public_key.chars().take(8).collect::<String>();
|
||||
let extension_id = format!("dev_{}_{}", key_prefix, manifest.name);
|
||||
|
||||
// 5. Check if dev extension already exists (allow reload)
|
||||
@ -404,13 +393,11 @@ pub fn remove_dev_extension(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ExtensionError> {
|
||||
// Only remove from dev_extensions, not production_extensions
|
||||
let mut dev_exts = state
|
||||
.extension_manager
|
||||
.dev_extensions
|
||||
.lock()
|
||||
.map_err(|e| ExtensionError::MutexPoisoned {
|
||||
let mut dev_exts = state.extension_manager.dev_extensions.lock().map_err(|e| {
|
||||
ExtensionError::MutexPoisoned {
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
}
|
||||
})?;
|
||||
|
||||
// Find and remove by public_key and name
|
||||
let to_remove = dev_exts
|
||||
@ -423,10 +410,7 @@ pub fn remove_dev_extension(
|
||||
eprintln!("✅ Dev extension removed: {}", name);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ExtensionError::NotFound {
|
||||
public_key,
|
||||
name,
|
||||
})
|
||||
Err(ExtensionError::NotFound { public_key, name })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -85,7 +85,10 @@
|
||||
>
|
||||
<!-- Overview Mode: Teleport to window preview -->
|
||||
<Teleport
|
||||
v-if="windowManager.showWindowOverview && overviewWindowState.has(window.id)"
|
||||
v-if="
|
||||
windowManager.showWindowOverview &&
|
||||
overviewWindowState.has(window.id)
|
||||
"
|
||||
:to="`#window-preview-${window.id}`"
|
||||
>
|
||||
<div
|
||||
@ -330,9 +333,9 @@ const getWorkspaceIcons = (workspaceId: string) => {
|
||||
.filter((item) => item.workspaceId === workspaceId)
|
||||
.map((item) => {
|
||||
if (item.itemType === 'system') {
|
||||
const systemWindow = windowManager.getAllSystemWindows().find(
|
||||
(win) => win.id === item.referenceId,
|
||||
)
|
||||
const systemWindow = windowManager
|
||||
.getAllSystemWindows()
|
||||
.find((win) => win.id === item.referenceId)
|
||||
|
||||
return {
|
||||
...item,
|
||||
@ -346,6 +349,7 @@ const getWorkspaceIcons = (workspaceId: string) => {
|
||||
(ext) => ext.id === item.referenceId,
|
||||
)
|
||||
|
||||
console.log('found ext', extension)
|
||||
return {
|
||||
...item,
|
||||
label: extension?.name || 'Unknown',
|
||||
@ -429,7 +433,9 @@ const handleDragOver = (event: DragEvent) => {
|
||||
const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
||||
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
|
||||
|
||||
try {
|
||||
@ -441,7 +447,9 @@ const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
||||
}
|
||||
|
||||
// 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 y = Math.max(0, event.clientY - desktopRect.top - 32)
|
||||
|
||||
@ -451,7 +459,7 @@ const handleDrop = async (event: DragEvent, workspaceId: string) => {
|
||||
item.id,
|
||||
x,
|
||||
y,
|
||||
workspaceId
|
||||
workspaceId,
|
||||
)
|
||||
} catch (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)
|
||||
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
|
||||
@ -635,7 +646,10 @@ watch(
|
||||
finalScale = MIN_PREVIEW_WIDTH / window.width
|
||||
}
|
||||
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, {
|
||||
|
||||
@ -89,7 +89,11 @@ const removeExtensionAsync = async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await extensionStore.removeExtensionAsync(extension.id, extension.version)
|
||||
await extensionStore.removeExtensionAsync(
|
||||
extension.publicKey,
|
||||
extension.name,
|
||||
extension.version,
|
||||
)
|
||||
await extensionStore.loadExtensionsAsync()
|
||||
|
||||
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 {
|
||||
let result: unknown
|
||||
|
||||
if (request.method.startsWith('extension.')) {
|
||||
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.')) {
|
||||
if (request.method.startsWith('haextension.context.')) {
|
||||
result = await handleContextMethodAsync(request)
|
||||
} else if (request.method.startsWith('storage.')) {
|
||||
} else if (request.method.startsWith('haextension.storage.')) {
|
||||
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 {
|
||||
throw new Error(`Unknown method: ${request.method}`)
|
||||
}
|
||||
@ -328,30 +326,27 @@ export const getExtensionWindow = (extensionId: string): Window | undefined => {
|
||||
return getAllInstanceWindows(extensionId)[0]
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Extension Methods
|
||||
// ==========================================
|
||||
// Broadcast context changes to all extension instances
|
||||
export const broadcastContextToAllExtensions = (context: {
|
||||
theme: string
|
||||
locale: string
|
||||
platform?: string
|
||||
}) => {
|
||||
const message = {
|
||||
type: 'haextension.context.changed',
|
||||
data: { context },
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
|
||||
async function handleExtensionMethodAsync(
|
||||
request: ExtensionRequest,
|
||||
extension: IHaexHubExtension, // Direkter Typ, kein ComputedRef mehr
|
||||
) {
|
||||
switch (request.method) {
|
||||
case 'extension.getInfo': {
|
||||
const info = (await invoke('get_extension_info', {
|
||||
publicKey: extension.publicKey,
|
||||
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,
|
||||
}
|
||||
console.log('[ExtensionHandler] Broadcasting context to all extensions:', context)
|
||||
|
||||
// Send to all registered extension windows
|
||||
for (const [_, instance] of iframeRegistry.entries()) {
|
||||
const win = windowIdToWindowMap.get(instance.windowId)
|
||||
if (win) {
|
||||
console.log('[ExtensionHandler] Sending context to:', instance.extension.name, instance.windowId)
|
||||
win.postMessage(message, '*')
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown extension method: ${request.method}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -369,7 +364,7 @@ async function handleDatabaseMethodAsync(
|
||||
}
|
||||
|
||||
switch (request.method) {
|
||||
case 'db.query': {
|
||||
case 'haextension.db.query': {
|
||||
const rows = await invoke<unknown[]>('extension_sql_select', {
|
||||
sql: params.query || '',
|
||||
params: params.params || [],
|
||||
@ -383,7 +378,7 @@ async function handleDatabaseMethodAsync(
|
||||
}
|
||||
}
|
||||
|
||||
case 'db.execute': {
|
||||
case 'haextension.db.execute': {
|
||||
await invoke<string[]>('extension_sql_execute', {
|
||||
sql: params.query || '',
|
||||
params: params.params || [],
|
||||
@ -397,7 +392,7 @@ async function handleDatabaseMethodAsync(
|
||||
}
|
||||
}
|
||||
|
||||
case 'db.transaction': {
|
||||
case 'haextension.db.transaction': {
|
||||
const statements =
|
||||
(request.params as { statements?: string[] }).statements || []
|
||||
|
||||
@ -467,7 +462,7 @@ async function handlePermissionsMethodAsync(
|
||||
|
||||
async function handleContextMethodAsync(request: ExtensionRequest) {
|
||||
switch (request.method) {
|
||||
case 'context.get':
|
||||
case 'haextension.context.get':
|
||||
if (!contextGetters) {
|
||||
throw new Error(
|
||||
'Context not initialized. Make sure useExtensionMessageHandler is called in a component.',
|
||||
@ -499,25 +494,25 @@ async function handleStorageMethodAsync(
|
||||
)
|
||||
|
||||
switch (request.method) {
|
||||
case 'storage.getItem': {
|
||||
case 'haextension.storage.getItem': {
|
||||
const key = request.params.key as string
|
||||
return localStorage.getItem(storageKey + key)
|
||||
}
|
||||
|
||||
case 'storage.setItem': {
|
||||
case 'haextension.storage.setItem': {
|
||||
const key = request.params.key as string
|
||||
const value = request.params.value as string
|
||||
localStorage.setItem(storageKey + key, value)
|
||||
return null
|
||||
}
|
||||
|
||||
case 'storage.removeItem': {
|
||||
case 'haextension.storage.removeItem': {
|
||||
const key = request.params.key as string
|
||||
localStorage.removeItem(storageKey + key)
|
||||
return null
|
||||
}
|
||||
|
||||
case 'storage.clear': {
|
||||
case 'haextension.storage.clear': {
|
||||
// Remove only instance-specific keys
|
||||
const keys = Object.keys(localStorage).filter((k) =>
|
||||
k.startsWith(storageKey),
|
||||
@ -526,7 +521,7 @@ async function handleStorageMethodAsync(
|
||||
return null
|
||||
}
|
||||
|
||||
case 'storage.keys': {
|
||||
case 'haextension.storage.keys': {
|
||||
// Return only instance-specific keys (without prefix)
|
||||
const keys = Object.keys(localStorage)
|
||||
.filter((k) => k.startsWith(storageKey))
|
||||
|
||||
@ -90,6 +90,7 @@ export const useExtensionsStore = defineStore('extensionsStore', () => {
|
||||
const extensions =
|
||||
await invoke<ExtensionInfoResponse[]>('get_all_extensions')
|
||||
|
||||
console.log('get_all_extensions', extensions)
|
||||
// ExtensionInfoResponse is now directly compatible with IHaexHubExtension
|
||||
availableExtensions.value = extensions
|
||||
} 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 { broadcastContextToAllExtensions } from '~/composables/extensionMessageHandler'
|
||||
import de from './de.json'
|
||||
import en from './en.json'
|
||||
|
||||
@ -9,6 +10,8 @@ export const useUiStore = defineStore('uiStore', () => {
|
||||
const isSmallScreen = breakpoints.smaller('sm')
|
||||
|
||||
const { $i18n } = useNuxtApp()
|
||||
const { locale } = useI18n()
|
||||
const { platform } = useDeviceStore()
|
||||
|
||||
$i18n.setLocaleMessage('de', {
|
||||
ui: de,
|
||||
@ -56,6 +59,15 @@ export const useUiStore = defineStore('uiStore', () => {
|
||||
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)
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user