5 Commits

Author SHA1 Message Date
38cc6f36d4 Bump version to 0.1.13 2025-11-10 10:22:43 +01:00
0d4059e518 Add TypeScript types for ExtensionError and improve error handling
- Add SerializedExtensionError TypeScript bindings from Rust
- Add ExtensionErrorCode enum export with ts-rs
- Create useExtensionError composable with type guards and error message extraction
- Fix developer page toast messages to show proper error messages instead of [object Object]
- Add getErrorMessage helper function for robust error handling across different error types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 02:31:32 +01:00
c551641737 Bump version to 0.1.13 and remove unused mobile.rs
- Update version in tauri.conf.json to 0.1.13
- Remove incomplete and unused mobile.rs file

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 02:14:31 +01:00
75093485bd Add showImage handler stub and mobile file provider foundation
- Add haextension.fs.showImage handler that delegates to frontend PhotoSwipe
- Add mobile.rs with open_file_with_provider command for future Android FileProvider integration
- Keep showImage as backwards-compatible no-op since image viewing is now handled client-side

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 02:13:44 +01:00
e1be08cb76 Add openFile support for opening files with system viewer
Added new filesystem handler for opening files with the system's default viewer:
- Implemented haextension.fs.openFile handler in filesystem.ts
- Writes files to temp directory and opens with openPath from opener plugin
- Added Tauri permissions: opener:allow-open-path with $TEMP/** scope
- Added filesystem permissions for temp directory access

This enables extensions to open files (like images) in the native system viewer where users can zoom and interact with them naturally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 23:58:40 +01:00
10 changed files with 134 additions and 8 deletions

View File

@ -1,7 +1,7 @@
{
"name": "haex-hub",
"private": true,
"version": "0.1.12",
"version": "0.1.13",
"type": "module",
"scripts": {
"build": "nuxt build",

View File

@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Error codes for frontend handling
*/
export type ExtensionErrorCode = "SecurityViolation" | "NotFound" | "PermissionDenied" | "MutexPoisoned" | "Database" | "Filesystem" | "FilesystemWithPath" | "Http" | "Shell" | "Manifest" | "Validation" | "InvalidPublicKey" | "InvalidSignature" | "InvalidActionString" | "SignatureVerificationFailed" | "CalculateHash" | "Installation";

View File

@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
/**
* Serialized representation of ExtensionError for TypeScript
*/
export type SerializedExtensionError = { code: number, type: string, message: string, extension_id: string | null, };

View File

@ -30,10 +30,15 @@
"fs:allow-resource-write-recursive",
"fs:allow-download-read-recursive",
"fs:allow-download-write-recursive",
"fs:allow-temp-read-recursive",
"fs:allow-temp-write-recursive",
"fs:default",
{
"identifier": "fs:scope",
"allow": [{ "path": "**" }]
"allow": [
{ "path": "**" },
{ "path": "$TEMP/**" }
]
},
"http:allow-fetch-send",
"http:allow-fetch",
@ -44,6 +49,12 @@
"notification:allow-is-permission-granted",
"notification:default",
"opener:allow-open-url",
{
"identifier": "opener:allow-open-path",
"allow": [
{ "path": "$TEMP/**" }
]
},
"opener:default",
"os:allow-hostname",
"os:default",

View File

@ -1 +1 @@
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-webview-show","core:webview:default","core:window:allow-create","core:window:allow-get-all-windows","core:window:allow-show","core:window:default","dialog:default","fs:allow-appconfig-read-recursive","fs:allow-appconfig-write-recursive","fs:allow-appdata-read-recursive","fs:allow-appdata-write-recursive","fs:allow-applocaldata-read-recursive","fs:allow-applocaldata-write-recursive","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-mkdir","fs:allow-exists","fs:allow-remove","fs:allow-resource-read-recursive","fs:allow-resource-write-recursive","fs:allow-download-read-recursive","fs:allow-download-write-recursive","fs:default",{"identifier":"fs:scope","allow":[{"path":"**"}]},"http:allow-fetch-send","http:allow-fetch","http:default","notification:allow-create-channel","notification:allow-list-channels","notification:allow-notify","notification:allow-is-permission-granted","notification:default","opener:allow-open-url","opener:default","os:allow-hostname","os:default","store:default"]}}
{"default":{"identifier":"default","description":"Capability for the main window","local":true,"windows":["main"],"permissions":["core:default","core:webview:allow-create-webview-window","core:webview:allow-create-webview","core:webview:allow-webview-show","core:webview:default","core:window:allow-create","core:window:allow-get-all-windows","core:window:allow-show","core:window:default","dialog:default","fs:allow-appconfig-read-recursive","fs:allow-appconfig-write-recursive","fs:allow-appdata-read-recursive","fs:allow-appdata-write-recursive","fs:allow-applocaldata-read-recursive","fs:allow-applocaldata-write-recursive","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-mkdir","fs:allow-exists","fs:allow-remove","fs:allow-resource-read-recursive","fs:allow-resource-write-recursive","fs:allow-download-read-recursive","fs:allow-download-write-recursive","fs:allow-temp-read-recursive","fs:allow-temp-write-recursive","fs:default",{"identifier":"fs:scope","allow":[{"path":"**"},{"path":"$TEMP/**"}]},"http:allow-fetch-send","http:allow-fetch","http:default","notification:allow-create-channel","notification:allow-list-channels","notification:allow-notify","notification:allow-is-permission-granted","notification:default","opener:allow-open-url",{"identifier":"opener:allow-open-path","allow":[{"path":"$TEMP/**"}]},"opener:default","os:allow-hostname","os:default","store:default"]}}

View File

@ -1,10 +1,12 @@
// src-tauri/src/extension/error.rs
use thiserror::Error;
use ts_rs::TS;
use crate::database::error::DatabaseError;
/// Error codes for frontend handling
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, TS)]
#[ts(export)]
pub enum ExtensionErrorCode {
SecurityViolation = 1000,
NotFound = 1001,
@ -25,6 +27,17 @@ pub enum ExtensionErrorCode {
Installation = 5000,
}
/// Serialized representation of ExtensionError for TypeScript
#[derive(Debug, Clone, serde::Serialize, TS)]
#[ts(export)]
pub struct SerializedExtensionError {
pub code: u16,
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
pub extension_id: Option<String>,
}
impl serde::Serialize for ExtensionErrorCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where

View File

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "haex-hub",
"version": "0.1.4",
"version": "0.1.13",
"identifier": "space.haex.hub",
"build": {
"beforeDevCommand": "pnpm dev",

View File

@ -163,8 +163,9 @@ const loadDevExtensionAsync = async () => {
extensionPath.value = ''
} catch (error) {
console.error('Failed to load dev extension:', error)
const { getErrorMessage } = useExtensionError()
add({
description: t('add.errors.loadFailed') + error,
description: `${t('add.errors.loadFailed')}: ${getErrorMessage(error)}`,
color: 'error',
})
} finally {
@ -196,8 +197,9 @@ const reloadDevExtensionAsync = async (extension: ExtensionInfoResponse) => {
})
} catch (error) {
console.error('Failed to reload dev extension:', error)
const { getErrorMessage } = useExtensionError()
add({
description: t('list.errors.reloadFailed') + error,
description: `${t('list.errors.reloadFailed')}: ${getErrorMessage(error)}`,
color: 'error',
})
}
@ -223,8 +225,9 @@ const removeDevExtensionAsync = async (extension: ExtensionInfoResponse) => {
await loadExtensionsAsync()
} catch (error) {
console.error('Failed to remove dev extension:', error)
const { getErrorMessage } = useExtensionError()
add({
description: t('list.errors.removeFailed') + error,
description: `${t('list.errors.removeFailed')}: ${getErrorMessage(error)}`,
color: 'error',
})
}

View File

@ -1,5 +1,7 @@
import { save } from '@tauri-apps/plugin-dialog'
import { writeFile } from '@tauri-apps/plugin-fs'
import { openPath } from '@tauri-apps/plugin-opener'
import { tempDir, join } from '@tauri-apps/api/path'
import type { IHaexHubExtension } from '~/types/haexhub'
import type { ExtensionRequest } from './types'
@ -42,6 +44,48 @@ export async function handleFilesystemMethodAsync(
}
}
case 'haextension.fs.showImage': {
// This method is now handled by the frontend using PhotoSwipe
// We keep it for backwards compatibility but it's a no-op
return {
success: true,
useFrontend: true,
}
}
case 'haextension.fs.openFile': {
const params = request.params as {
data: number[]
fileName: string
mimeType?: string
}
try {
// Convert number array back to Uint8Array
const data = new Uint8Array(params.data)
// Get temp directory and create file path
const tempDirPath = await tempDir()
const tempFilePath = await join(tempDirPath, params.fileName)
// Write file to temp directory
await writeFile(tempFilePath, data)
// Open file with system's default viewer
await openPath(tempFilePath)
return {
success: true,
}
}
catch (error) {
console.error('[Filesystem] Error opening file:', error)
return {
success: false,
}
}
}
default:
throw new Error(`Unknown filesystem method: ${request.method}`)
}

View File

@ -0,0 +1,43 @@
import type { SerializedExtensionError } from '~~/src-tauri/bindings/SerializedExtensionError'
/**
* Type guard to check if error is a SerializedExtensionError
*/
export function isSerializedExtensionError(error: unknown): error is SerializedExtensionError {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
'message' in error &&
'type' in error
)
}
/**
* Extract error message from unknown error type
*/
export function getErrorMessage(error: unknown): string {
if (isSerializedExtensionError(error)) {
return error.message
}
if (error instanceof Error) {
return error.message
}
if (typeof error === 'string') {
return error
}
return String(error)
}
/**
* Composable for handling extension errors
*/
export function useExtensionError() {
return {
isSerializedExtensionError,
getErrorMessage,
}
}