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

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 at lines 89-118:

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 (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:

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 (lines 83-106) wraps the manager with request deduplication via permissionRequestInFlight:

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 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:

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

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

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

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

Notification Permission Hook

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, VoiceDeviceManager.tsx, and 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 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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →