/** * Transport between the web UI and the Rust Core. * * Every call goes to one named Tauri command; the Rust Core identifies the calling * window and checks its permissions. This wrapper is a convenience, NOT a security * boundary. * * Errors are converted to the message format of the Electron 0.1.0 IPC layer * ("Error invoking remote method '': Error: "), because existing UI * code and third-party modules parse error codes out of that text. */ import { invoke } from "@tauri-apps/api/core"; import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; import type { CoreErrorPayload } from "./types"; export type Unsubscribe = () => void; function errorFrom(channel: string, reason: unknown): Error { const payload = (reason && typeof reason === "object" ? reason : { message: String(reason ?? "Unknown error") }) as Partial; const message = typeof payload.message === "string" && payload.message ? payload.message : String(payload.code || "Operation failed."); return new Error(`Error invoking remote method '${channel}': Error: ${message}`); } /** Invokes a Core command. `channel` is the stable 0.1.0 channel name used in error texts. */ export async function call(command: string, channel: string, payload?: unknown): Promise { try { return await invoke(command, payload === undefined ? {} : { payload }); } catch (reason) { throw errorFrom(channel, reason); } } /** Fire-and-forget command (Electron `ipcRenderer.send`): failures are logged by the Core only. */ export function send(command: string, payload: unknown): void { void invoke(command, { payload }).catch(() => {}); } /** Subscribes to a Core push event; returns a synchronous unsubscribe function like 0.1.0. */ export function subscribe(event: string, callback: (payload: T) => void): Unsubscribe { if (typeof callback !== "function") return () => {}; let active = true; let unlisten: UnlistenFn | null = null; // Without a target a window hears events addressed to every other window as well. void listen(event, (message) => { if (active) callback(message.payload); }, { target: { kind: "WebviewWindow", label: getCurrentWebviewWindow().label } }) .then((fn) => { if (active) unlisten = fn; else fn(); }) .catch(() => {}); return () => { active = false; if (unlisten) { unlisten(); unlisten = null; } }; } /** Like `subscribe`, but throws for a non-function callback (module API contract of 0.1.0). */ export function subscribeStrict(event: string, callback: (payload: T) => void, what: string): Unsubscribe { if (typeof callback !== "function") throw new TypeError(`${what} callback must be a function.`); return subscribe(event, callback); }