36 lines
1.6 KiB
TypeScript
36 lines
1.6 KiB
TypeScript
|
|
import { useEffect, useRef } from 'react';
|
||
|
|
|
||
|
|
// Tauri v2 injects `__TAURI_INTERNALS__` into the webview at runtime; we use it
|
||
|
|
// directly so cinny doesn't need `@tauri-apps/api` as a dependency. Native Rust
|
||
|
|
// modules push data back to the web by dispatching DOM CustomEvents (see
|
||
|
|
// `emit_to_web` in cinny-desktop's `native` module), which `useTauriEvent`
|
||
|
|
// subscribes to. This module is the single source for the desktop bridge that
|
||
|
|
// every `useTauri*` feature hook builds on.
|
||
|
|
type Invoke = (cmd: string, args?: Record<string, unknown>) => Promise<unknown>;
|
||
|
|
|
||
|
|
export const tauriInvoke = (): Invoke | undefined =>
|
||
|
|
(window as unknown as { __TAURI_INTERNALS__?: { invoke: Invoke } }).__TAURI_INTERNALS__?.invoke;
|
||
|
|
|
||
|
|
export const isTauri = (): boolean => tauriInvoke() !== undefined;
|
||
|
|
|
||
|
|
/** Fire-and-forget invoke that no-ops (and never throws) outside Tauri. */
|
||
|
|
export const invokeTauri = (cmd: string, args?: Record<string, unknown>): void => {
|
||
|
|
tauriInvoke()?.(cmd, args).catch(() => undefined);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Subscribe to a CustomEvent dispatched from the Rust side via `emit_to_web`.
|
||
|
|
* The handler is kept in a ref so callers don't need to memoize it to avoid
|
||
|
|
* re-subscribing. No-op outside Tauri.
|
||
|
|
*/
|
||
|
|
export function useTauriEvent<T = unknown>(name: string, handler: (detail: T) => void): void {
|
||
|
|
const handlerRef = useRef(handler);
|
||
|
|
handlerRef.current = handler;
|
||
|
|
useEffect(() => {
|
||
|
|
if (!isTauri()) return undefined;
|
||
|
|
const listener = (e: Event): void => handlerRef.current((e as CustomEvent<T>).detail);
|
||
|
|
window.addEventListener(name, listener);
|
||
|
|
return () => window.removeEventListener(name, listener);
|
||
|
|
}, [name]);
|
||
|
|
}
|