fix(desktop): prevent transformCallback crash in browser mode
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Root cause: ChatArea.tsx called listen() from @tauri-apps/api/event directly on component mount without checking isTauriRuntime(). When accessed from a regular browser (not Tauri WebView), window.__TAURI_INTERNALS__ is undefined, causing "Cannot read properties of undefined (reading 'transformCallback')". Solution: - Created lib/safe-tauri.ts with safe wrappers (safeInvoke, safeListen, safeListenEvent, requireInvoke) that gracefully degrade when Tauri IPC is unavailable - Replaced direct listen() call in ChatArea.tsx with safeListenEvent()
This commit is contained in:
@@ -7,7 +7,8 @@ import { useArtifactStore } from '../store/chat/artifactStore';
|
||||
import { useConnectionStore } from '../store/connectionStore';
|
||||
import { useAgentStore } from '../store/agentStore';
|
||||
import { useConfigStore } from '../store/configStore';
|
||||
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { type UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { safeListenEvent } from '../lib/safe-tauri';
|
||||
import { Paperclip, SquarePen, ArrowUp, MessageSquare, Download, X, FileText, Image as ImageIcon } from 'lucide-react';
|
||||
import { Button, EmptyState, MessageListSkeleton, LoadingDots } from './ui';
|
||||
import { ResizableChatLayout } from './ai/ResizableChatLayout';
|
||||
@@ -160,7 +161,7 @@ export function ChatArea() {
|
||||
// Listen for hand-execution-complete Tauri events
|
||||
useEffect(() => {
|
||||
let unlisten: UnlistenFn | undefined;
|
||||
listen<{ approvalId: string; handId: string; success: boolean; error?: string | null }>(
|
||||
safeListenEvent<{ approvalId: string; handId: string; success: boolean; error?: string | null }>(
|
||||
'hand-execution-complete',
|
||||
(event) => {
|
||||
const { handId, success, error } = event.payload;
|
||||
|
||||
104
desktop/src/lib/safe-tauri.ts
Normal file
104
desktop/src/lib/safe-tauri.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Safe Tauri API Wrappers
|
||||
*
|
||||
* All Tauri APIs (invoke, listen, etc.) depend on `window.__TAURI_INTERNALS__`
|
||||
* which only exists inside the Tauri WebView. When the frontend is accessed
|
||||
* from a regular browser (e.g. http://localhost:1420/ for debugging), these
|
||||
* APIs throw cryptic errors like:
|
||||
*
|
||||
* TypeError: Cannot read properties of undefined (reading 'transformCallback')
|
||||
*
|
||||
* This module provides drop-in replacements that gracefully degrade when
|
||||
* the Tauri runtime is not available.
|
||||
*/
|
||||
|
||||
import { isTauriRuntime } from './tauri-gateway';
|
||||
import { createLogger } from './logger';
|
||||
|
||||
const log = createLogger('safe-tauri');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Safe invoke
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Type-safe wrapper around Tauri `invoke`.
|
||||
* Returns `null` (with a debug log) when not in Tauri runtime.
|
||||
*/
|
||||
export async function safeInvoke<T>(
|
||||
cmd: string,
|
||||
args?: Record<string, unknown>,
|
||||
): Promise<T | null> {
|
||||
if (!isTauriRuntime()) {
|
||||
log.debug(`invoke("${cmd}") skipped — not in Tauri runtime`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
return invoke<T>(cmd, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like `safeInvoke` but throws when not in Tauri runtime.
|
||||
* Use for operations that MUST have a Tauri backend.
|
||||
*/
|
||||
export async function requireInvoke<T>(
|
||||
cmd: string,
|
||||
args?: Record<string, unknown>,
|
||||
): Promise<T> {
|
||||
if (!isTauriRuntime()) {
|
||||
throw new Error(`invoke("${cmd}") failed — not running in Tauri WebView`);
|
||||
}
|
||||
|
||||
const { invoke } = await import('@tauri-apps/api/core');
|
||||
return invoke<T>(cmd, args);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Safe listen
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Wrapper around Tauri `listen`.
|
||||
* Returns a no-op `UnlistenFn` when not in Tauri runtime.
|
||||
*
|
||||
* Usage — replace:
|
||||
* import { listen } from '@tauri-apps/api/event';
|
||||
* With:
|
||||
* import { safeListen } from '../lib/safe-tauri';
|
||||
*/
|
||||
export async function safeListen<T>(
|
||||
event: string,
|
||||
handler: (payload: T) => void,
|
||||
): Promise<() => void> {
|
||||
if (!isTauriRuntime()) {
|
||||
log.debug(`listen("${event}") skipped — not in Tauri runtime`);
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const { listen } = await import('@tauri-apps/api/event');
|
||||
return listen<T>(event, (e) => handler(e.payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around Tauri `listen` that provides the full Event object.
|
||||
* Returns a no-op `UnlistenFn` when not in Tauri runtime.
|
||||
*/
|
||||
export async function safeListenEvent<T>(
|
||||
event: string,
|
||||
handler: (event: { event: string; payload: T }) => void,
|
||||
): Promise<() => void> {
|
||||
if (!isTauriRuntime()) {
|
||||
log.debug(`listen("${event}") skipped — not in Tauri runtime`);
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const { listen } = await import('@tauri-apps/api/event');
|
||||
return listen<T>(event, (e) => handler(e));
|
||||
}
|
||||
Reference in New Issue
Block a user