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()
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
/**
|
|
* 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));
|
|
}
|