# Window Permission Handler Architecture in Fluxer: Media, Notifications, and WebAuthn

> Explore Fluxer's window permission handler architecture for media, notifications, and WebAuthn. Discover its two-stage pattern for detecting environment capabilities and delegating to native APIs or web interfaces.

- Repository: [Fluxer/fluxer](https://github.com/fluxerapp/fluxer)
- Tags: architecture
- Published: 2026-03-17

---

**Fluxer implements a unified two-stage pattern for window-level permissions that detects environment capabilities in Electron or browsers, then delegates to native APIs or standard web interfaces before propagating results through centralized state stores.**

The `fluxerapp/fluxer` repository abstracts complex permission workflows for desktop notifications, microphone/camera access, and WebAuthn passkeys into a cohesive architecture. This system consistently handles capability detection, permission acquisition, and state synchronization across both Electron and browser environments through dedicated utility modules and observable stores.

## Notification Permission Handling

### Two-Stage Detection and Request Pattern

The notification flow begins when UI components call `NotificationUtils.requestPermission(i18n)`. The utility first checks the runtime environment using `isDesktop()`. In Electron, it immediately dispatches a Redux `permissionGranted` action, plays notification sounds, and displays a native system notification via `electronApi.showNotification`. In browsers, it delegates to `requestBrowserPermission()`, which wraps the standard `Notification.requestPermission()` API.

### Implementation in NotificationUtils.tsx

The core logic resides in [`fluxer_app/src/utils/NotificationUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/utils/NotificationUtils.tsx) at lines 89-118:

```tsx
export async function requestPermission(i18n: I18n): Promise<void> {
    if (isDesktop()) {
        NotificationActionCreators.permissionGranted();
        playNotificationSoundIfEnabled();
        const icon = getCurrentUserAvatar() ?? '';
        void showNotification({
            title: i18n._(msg`Access granted`),
            body: i18n._(msg`Huzzah! Desktop notifications are enabled`),
            icon,
        });
        return;
    }

    const result = await requestBrowserPermission();
    if (result !== 'granted') {
        NotificationActionCreators.permissionDenied(i18n);
        return;
    }

    NotificationActionCreators.permissionGranted();
    playNotificationSoundIfEnabled();
    const icon = getCurrentUserAvatar() ?? '';
    void showNotification({
        title: i18n._(msg`Access granted`),
        body: i18n._(msg`Huzzah! Browser notifications are enabled`),
        icon,
    });
}

```

Results propagate through `NotificationActionCreators.permissionGranted()` or `permissionDenied()`, which update the global Redux store and trigger UI updates such as hiding notification nagbars.

## Media Device Permission Handling

### VoiceDeviceManager Enumeration Strategy

Media permissions follow a more complex flow involving device enumeration. When `VoiceDevicePermissionStore.requestPermissionFor('audio' | 'video')` is invoked, it delegates to `VoiceDeviceManager.ensureDevices({requestPermissions: true})`. The manager at [`fluxer_app/src/utils/VoiceDeviceManager.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/utils/VoiceDeviceManager.tsx) (lines 48-85) implements a dual-path strategy:

1. **Capability detection**: Enumerate devices via `navigator.mediaDevices.enumerateDevices()`
2. **Permission acquisition**: If device labels are missing (indicating no permission), request access via:
   - **Electron**: `ensureNativePermission('microphone')` or `ensureNativePermission('camera')` from [`fluxer_app/src/utils/NativePermissions.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/utils/NativePermissions.tsx)
   - **Browser**: `navigator.mediaDevices.getUserMedia({audio:true, video:true})`

After acquiring the stream (and immediately stopping tracks to release devices), the manager updates `permissionStatus` to `'granted'`, `'denied'`, or `'loading'` and notifies listeners.

### Store-Level Coordination

`VoiceDevicePermissionStore` at [`fluxer_app/src/stores/voice/VoiceDevicePermissionStore.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/stores/voice/VoiceDevicePermissionStore.tsx) (lines 83-106) wraps the manager with request deduplication via `permissionRequestInFlight`:

```tsx
async requestPermissionFor(type: 'audio' | 'video'): Promise<boolean> {
    if (this.permissionRequestInFlight) return this.permissionRequestInFlight;
    const requestPromise = (async () => {
        const state = await this.ensureDevices({requestPermissions: true});
        if (state.permissionStatus === 'granted') {
            type === 'audio'
                ? MediaPermissionStore.updateMicrophonePermissionGranted()
                : MediaPermissionStore.updateCameraPermissionGranted();
            return true;
        }
        if (state.permissionStatus === 'denied') {
            type === 'audio'
                ? MediaPermissionStore.markMicrophoneExplicitlyDenied()
                : MediaPermissionStore.markCameraExplicitlyDenied();
            return false;
        }
        return type === 'audio'
            ? MediaPermissionStore.isMicrophoneGranted()
            : MediaPermissionStore.isCameraGranted();
    })()
    .catch(err => {
        logger.error('Failed to request media permission', {type, err});
        return false;
    })
    .finally(() => { this.permissionRequestInFlight = null; });

    this.permissionRequestInFlight = requestPromise;
    return requestPromise;
}

```

This ensures concurrent requests collapse into a single permission flow while updating `MediaPermissionStore` with high-level granted/denied flags consumed by the UI layer.

## WebAuthn Permission Handling

### Capability Detection and Native Delegation

WebAuthn implementation in [`fluxer_app/src/utils/WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/fluxer_app/src/utils/WebAuthnUtils.tsx) follows the same two-stage pattern but adds native Electron passkey support. The `assertWebAuthnSupported()` function checks:

- **Electron**: `electronApi.passkeyIsSupported()` for native passkey APIs
- **Browser**: `browserSupportsWebAuthn()` from `@simplewebauthn/browser`

Authentication and registration methods then delegate accordingly:

```tsx
export async function performRegistration(options: PublicKeyCredentialCreationOptionsJSON): Promise<RegistrationResponseJSON> {
    await assertWebAuthnSupported();
    if (Platform.isElectron) {
        const electronApi = getElectronAPI();
        const nativeSupported = electronApi && (await electronApi.passkeyIsSupported?.());
        if (nativeSupported && electronApi.passkeyRegister) {
            return electronApi.passkeyRegister(options);
        }
    }
    return await startRegistration({optionsJSON: options});
}

```

Error handling bubbles up to callers, allowing UI components to display specific feedback when WebAuthn is unsupported or permission is denied.

## Cross-Platform Permission State Management

The architecture centralizes permission outcomes through distinct state layers:

- **Notification flow**: Redux actions (`NotificationActionCreators`) update global store for notification nagbars and settings
- **Media flow**: `MediaPermissionStore` maintains boolean flags for microphone/camera access, while `VoiceDeviceManager` tracks detailed `permissionStatus` observable
- **WebAuthn flow**: Synchronous capability checks prevent unsupported API calls, with errors handled at the UI layer rather than stored globally

All utilities abstract platform detection (`isDesktop()`, `Platform.isElectron`) and native bridging (`getElectronAPI()`, `ensureNativePermission()`), keeping UI components agnostic of environment specifics.

## Implementation Examples

### Requesting Microphone Access

```tsx
import VoiceDevicePermissionStore from '@app/stores/voice/VoiceDevicePermissionStore';

async function enableMicrophone() {
  const granted = await VoiceDevicePermissionStore.requestPermissionFor('audio');
  if (granted) {
    console.log('Microphone ready');
  } else {
    console.warn('User denied microphone access');
  }
}

```

### WebAuthn Passkey Registration

```tsx
import {performRegistration} from '@app/utils/WebAuthnUtils';

async function enrollPasskey() {
  const options = await fetchRegistrationOptionsFromBackend();
  const credential = await performRegistration(options);
  await sendCredentialToBackend(credential);
}

```

### Notification Permission Hook

```tsx
import {useEffect} from 'react';
import {requestPermission} from '@app/utils/NotificationUtils';
import {useI18n} from '@lingui/react';

export function useNotificationPermission() {
  const {i18n} = useI18n();

  useEffect(() => {
    requestPermission(i18n).catch(() => {
      // Handle denied/unsupported gracefully
    });
  }, [i18n]);
}

```

## Summary

- **Two-stage architecture**: All permission handlers first detect environment capabilities, then acquire permissions through appropriate native or web APIs.
- **Centralized utilities**: [`NotificationUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/NotificationUtils.tsx), [`VoiceDeviceManager.tsx`](https://github.com/fluxerapp/fluxer/blob/main/VoiceDeviceManager.tsx), and [`WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/WebAuthnUtils.tsx) encapsulate platform-specific logic for Electron and browser environments.
- **State propagation**: Permission results flow through Redux actions (notifications) or MobX stores (media devices) to update UI components consistently.
- **Request deduplication**: Media permissions use `permissionRequestInFlight` in `VoiceDevicePermissionStore` to prevent concurrent permission prompts.
- **Native bridging**: Electron-specific implementations in [`NativePermissions.tsx`](https://github.com/fluxerapp/fluxer/blob/main/NativePermissions.tsx) and `getElectronAPI()` abstract OS-level permission dialogs from the core business logic.

## Frequently Asked Questions

### How does Fluxer handle permission differences between Electron and browser environments?

Fluxer uses platform detection utilities like `isDesktop()` and `Platform.isElectron` to branch logic at runtime. In Electron, permissions often delegate to native APIs such as `electronApi.showNotification` or `ensureNativePermission()`, while browser environments use standard web APIs like `Notification.requestPermission()` and `navigator.mediaDevices.getUserMedia()`. This abstraction allows the same UI components to function across both environments without environment-specific code.

### What is the two-stage pattern used in Fluxer permission handlers?

Every permission flow implements capability detection followed by permission acquisition. First, the handler verifies the environment supports the feature—checking WebAuthn support via `browserSupportsWebAuthn()` or detecting if device labels exist via `enumerateDevices()`. Second, if capabilities exist but permissions are missing, the handler requests access through the appropriate native or browser API. This prevents unnecessary permission prompts when features are unsupported.

### How are media device permissions coordinated between VoiceDeviceManager and VoiceDevicePermissionStore?

`VoiceDevicePermissionStore` acts as the public API and request coordinator, maintaining the `permissionRequestInFlight` promise to deduplicate concurrent requests. It delegates actual device enumeration and permission negotiation to `VoiceDeviceManager`, which handles the low-level logic of calling `getUserMedia` or native permissions. Once `VoiceDeviceManager` updates its internal `permissionStatus`, the store translates these states into high-level boolean flags in `MediaPermissionStore` (e.g., `updateMicrophonePermissionGranted()`), which UI components observe for reactive updates.

### What happens when WebAuthn is not supported in the current environment?

The `assertWebAuthnSupported()` function in [`WebAuthnUtils.tsx`](https://github.com/fluxerapp/fluxer/blob/main/WebAuthnUtils.tsx) checks for native Electron passkey support or browser WebAuthn compatibility before any credential operations. If neither is available, it throws `'WebAuthn is not supported in this environment.'` This error bubbles up to the calling UI component, which can catch the exception and display appropriate user feedback or fallback authentication options.