jamiepine/voicebox · critical · Error
Navigator API is not available. This might be a Tauri config
Error message
Navigator API is not available. This might be a Tauri configuration issue.
What it means
Thrown at the very top of `startRecording()` in `useAudioRecording.ts` (line 34-39) when `typeof navigator === 'undefined'`. In any real browser/Tauri webview the global `navigator` always exists, so hitting this branch means the code is executing outside a client DOM environment — most often during SSR/prerender or in a JS runtime without a DOM.
Source
Thrown at app/src/lib/hooks/useAudioRecording.ts:38
const streamRef = useRef<MediaStream | null>(null);
const timerRef = useRef<number | null>(null);
const startTimeRef = useRef<number | null>(null);
const cancelledRef = useRef<boolean>(false);
const startRecording = useCallback(async () => {
try {
setError(null);
chunksRef.current = [];
cancelledRef.current = false;
setDuration(0);
// Check if getUserMedia is available
// In Tauri, navigator.mediaDevices might not be available immediately
if (typeof navigator === 'undefined') {
const errorMsg =
'Navigator API is not available. This might be a Tauri configuration issue.';
setError(errorMsg);
throw new Error(errorMsg);
}
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
// Try waiting a bit for Tauri webview to initialize
await new Promise((resolve) => setTimeout(resolve, 100));
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.error('MediaDevices check:', {
hasNavigator: typeof navigator !== 'undefined',
hasMediaDevices: !!navigator?.mediaDevices,
hasGetUserMedia: !!navigator?.mediaDevices?.getUserMedia,
isTauri: platform.metadata.isTauri,
});
const errorMsg = platform.metadata.isTauri
? 'Microphone access is not available. Please ensure:\n1. The app has microphone permissions in System Settings (macOS: System Settings > Privacy & Security > Microphone)\n2. You restart the app after granting permissions\n3. You are using Tauri v2 with a webview that supports getUserMedia'
: 'Microphone access is not available. Please ensure you are using a secure context (HTTPS or localhost) and that your browser has microphone permissions enabled.';
setError(errorMsg);View on GitHub (pinned to 51f49dea19)
Solutions
- Only call `startRecording()` from a user gesture inside a client-mounted component (e.g. a button onClick), never during render or module load.
- If using SSR, ensure the recording UI is client-only (`'use client'`, or dynamic import with `ssr: false`).
- In tests, run under jsdom/happy-dom or mock `navigator`.
- Confirm the Tauri webview is a standard WKWebView/WebView2 — a custom host lacking `navigator` is a build/config defect.
Example fix
// before — runs during render/SSR
const { startRecording } = useAudioRecording();
startRecording();
// after — guard to client + user gesture
if (typeof navigator === 'undefined') return; // SSR/prerender
<button onClick={() => startRecording()}>Record</button> Defensive patterns
Strategy: type-guard
Validate before calling
// Never call startRecording outside a client-mounted, user-gesture context
if (typeof navigator === 'undefined') {
// SSR/prerender — bail silently instead of throwing
return;
} Type guard
function hasNavigator(): boolean {
return typeof navigator !== 'undefined';
} Try / catch
// In the component: only invoke from a gesture, after client mount
<button disabled={!hasNavigator()} onClick={() => startRecording()}>Record</button> Prevention
- Gate recording UI behind a client-only mount (`'use client'`, `useEffect`).
- Only call `startRecording()` from a user gesture (onClick/onPointerDown).
- In tests, run under jsdom or mock `navigator`.
When it happens
Trigger: The hook's `startRecording` runs during server-side rendering or static prerendering (Next.js/React SSR) where `navigator` is undefined; the code is unit-tested in Node without jsdom; the Tauri webview host failed to inject the standard web globals (misconfigured/custom host).
Common situations: A component using this hook is rendered on the server without a `'use client'`/`useEffect` guard; a test imports the hook and calls `startRecording` synchronously in a Node runner; an exotic embedded webview strips `navigator`.
Related errors
- Microphone access is not available. Please ensure: 1. The ap
- HTTP error! status: ${response.status}
- HTTP ${res.status}
- Failed to resolve save path from dialog
- HTTP ${res.status}
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/6e3b171181f17c63.
Report an issue: GitHub.