How the Tauri v2 Desktop App Integrates with the React Frontend for Filesystem Dialogs in GeoLibre
GeoLibre uses a thin TypeScript bridge in apps/geolibre-desktop/src/lib/tauri-io.ts to let React components invoke native file dialogs through Tauri v2's invoke API and @tauri-apps/plugin-dialog, with automatic browser fallbacks when running outside the desktop shell.
The opengeos/GeoLibre repository is a geospatial data viewer that ships both as a Tauri v2 desktop application and a browser-based build. A critical integration point is how the React frontend requests filesystem access—file open, save, and folder selection dialogs—without sacrificing security or cross-platform compatibility. This article examines the exact mechanism, key source files, and code patterns used to bridge React and Tauri's native capabilities.
Tauri v2 Command Registration and Security Model
Tauri v2 exposes native functionality through commands—Rust functions registered with the Tauri runtime that JavaScript can call via invoke. In GeoLibre, the Rust side (located in src-tauri/src/main.rs and related modules) registers commands like read_local_file, fetch_url_bytes, and allow_raster_asset.
These commands follow Tauri's Powerbox security model: the renderer process never holds blanket filesystem permissions. Instead, the user grants access through native dialogs, and Tauri returns validated file handles that subsequent operations must use. This ensures the React UI cannot access arbitrary paths without explicit user consent.
The TypeScript Bridge: tauri-io.ts
All frontend-to-Tauri communication for filesystem operations flows through apps/geolibre-desktop/src/lib/tauri-io.ts. This module centralizes dialog invocation and provides graceful degradation when the code runs in a browser environment.
Core Imports and Wrappers
// apps/geolibre-desktop/src/lib/tauri-io.ts
import { open, save } from "@tauri-apps/plugin-dialog";
import { invoke } from "@tauri-apps/api/core";
The file imports two distinct APIs:
@tauri-apps/plugin-dialog– Providesopen()andsave()for native dialog windows@tauri-apps/api/core– Providesinvoke()for calling custom Rust commands
File Open Dialog with Extension Filtering
export async function openFileDialog(
filters: FileFilter[]
): Promise<string[] | null> {
try {
const paths = await open({ multiple: true, filters });
return paths ?? null; // null indicates user cancellation
} catch (e) {
console.warn("File dialog requires Tauri runtime");
return null;
}
}
Key behaviors:
multiple: trueallows batch selectionfiltersrestricts selectable file types (e.g., GeoJSON, Shapefile, Zarr)- The
catchblock handles browser environments where Tauri APIs are unavailable
Save Dialog for Export Operations
export async function saveFileDialog(
defaultName: string,
filters: FileFilter[]
): Promise<string | null> {
const path = await save({ defaultPath: defaultName, filters });
return path ?? null;
}
Invoking Rust Commands for File Reading
After obtaining a path from the dialog, the frontend reads file contents through a Rust command:
export async function readLocalFile(path: string): Promise<ArrayBuffer> {
return await invoke<ArrayBuffer>("read_local_file", { path });
}
The invoke call serializes the path argument, passes it to the Rust runtime, which validates the handle and returns the file's bytes as an ArrayBuffer.
Directory Picking: zarr-directory-picker.ts
For folder selection—critical for Zarr dataset navigation—GeoLibre uses a dedicated module:
// apps/geolibre-desktop/src/lib/zarr-directory-picker.ts
import { open } from "@tauri-apps/plugin-dialog";
export async function pickZarrDirectory(): Promise<string | null> {
const path = await open({ directory: true });
return path ?? null;
}
Setting directory: true invokes the platform-native folder picker. The returned path feeds into Zarr parsing logic that traverses the hierarchical array store.
React Hook Integration Pattern
The React UI does not call tauri-io.ts functions directly. Instead, custom hooks abstract the interaction and dispatch results to the central store.
Hook-to-Store Data Flow
- User clicks "Add Data" button
- Hook calls
openFileDialog()fromtauri-io.ts - On success, hook dispatches to
@geolibre/corestore - Store thunk invokes
readLocalFile()via the same bridge - Binary data flows back to React for rendering
// Example pattern from hooks like usePlugins.ts
import { openFileDialog, readLocalFile } from "@/lib/tauri-io";
import { useAppStore } from "@geolibre/core";
export function useDataImport() {
const addLayer = useAppStore(state => state.addLayer);
const importVectorFiles = async () => {
const files = await openFileDialog([
{ name: "GeoJSON", extensions: ["geojson", "json"] },
{ name: "Shapefile", extensions: ["shp"] }
]);
if (!files) return;
for (const path of files) {
const buffer = await readLocalFile(path);
// Parse and add to map layers...
addLayer({ type: "vector", source: buffer, path });
}
};
return { importVectorFiles };
}
This store-driven architecture decouples UI components from Tauri specifics—identical React code works in the desktop app and browser builds.
Browser Fallback Mechanism
When tauri-io.ts detects that Tauri APIs are unavailable (caught exceptions or undefined globals), the same module can delegate to standard Web APIs:
- File selection: Hidden
<input type="file">element - Directory selection: Experimental File System Access API (
showDirectoryPicker) - Save/export:
URL.createObjectURLwith anchor download, orshowSaveFilePicker
This dual-target compilation allows GeoLibre to ship a single React codebase for both Tauri v2 desktop and embedded Jupyter/web deployments.
Security and Sandboxing Implementation
| Layer | Protection |
|---|---|
| Tauri runtime | Renderer process runs without filesystem permissions |
| Dialog grants | User action (click) required to obtain file handles |
| Command validation | Rust read_local_file validates handles before access |
| Path isolation | React never constructs absolute paths; only uses dialog returns |
The React frontend receives opaque path strings—it cannot traverse parent directories or access sibling files. All file operations route through validated Tauri commands.
Summary
- Central bridge:
apps/geolibre-desktop/src/lib/tauri-io.tswraps all Tauri v2 dialog and invoke calls - Plugin architecture: Uses
@tauri-apps/plugin-dialogfor native selectors,@tauri-apps/api/corefor custom Rust commands - Hook abstraction: React components use store-connected hooks that delegate to the bridge
- Cross-platform: Automatic fallback to Web APIs when Tauri runtime is absent
- Security model: Powerbox-style grant validation in Rust commands prevents unauthorized access
Frequently Asked Questions
How does GeoLibre handle file dialogs in browser builds?
GeoLibre's tauri-io.ts module catches exceptions when Tauri APIs are unavailable and falls back to standard Web APIs like <input type="file">, showDirectoryPicker, or showSaveFilePicker. The same React components work without modification because the bridge abstracts the platform difference.
What Tauri v2 plugin provides the folder picker for Zarr datasets?
The @tauri-apps/plugin-dialog package provides folder selection through open({ directory: true }). GeoLibre wraps this in apps/geolibre-desktop/src/lib/zarr-directory-picker.ts with Zarr-specific error handling and path validation.
Why doesn't the React frontend read files directly?
Direct filesystem access from a web renderer violates OS security models. Tauri v2 enforces a command-based architecture where all file operations execute in the Rust host process with explicitly granted permissions. React receives only ArrayBuffer data or path references that the runtime has pre-validated.
Can I use the same dialog code for both single and batch file selection?
Yes. The openFileDialog wrapper in tauri-io.ts passes multiple: true to the underlying open() call, returning string[] | null. For single-file scenarios, pass multiple: false or simply use the first element of the returned array.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →