import { loadDevicePrivateKey } from '@chat-app/shared/auth'; import { sendEncryptedMessage } from '@chat-app/shared/chat'; import { type CallKind, type CallSignal, fetchLivekitToken, signalTopic, } from '@chat-app/shared/rtc'; import type { RealtimeChannel } from '@supabase/supabase-js'; import { ConnectionState, type RemoteParticipant, type RemoteTrack, type RemoteTrackPublication, Room, RoomEvent, Track, } from 'livekit-client'; import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react'; import { useAuth } from './AuthContext'; import { useConversationsContext } from './ConversationsContext'; import { playEndBeep, playJoinBeep, playLeaveBeep } from '../lib/callSounds'; import { setCallWakeLock } from '../lib/wakeLock'; import { notify } from '../lib/osNotify'; import { isTauriRuntime, registerGlobalShortcutPress, registerPttShortcut, unregisterGlobalShortcut, unregisterPttShortcut, } from '../lib/globalShortcut'; import { getPttSettings, subscribePttSettings } from '../lib/pttSettings'; import { bindingToTauriShortcut, eventMatchesBinding, getVoiceHotkeys, subscribeVoiceHotkeys, type VoiceHotkeys, } from '../lib/voiceHotkeys'; import { getAudioQualityParams, getAudioSettings, subscribeAudioSettings, updateAudioSettings, } from '../lib/audioSettings'; import { applyBackgroundBlurToLocal, removeBackgroundBlurFromLocal, } from '../lib/videoBlur'; import { createCallE2EE, getCallE2EESettings, isE2EESupported, } from '../lib/callE2EE'; import { createMicPipeline, type MicPipeline } from '../lib/micPipeline'; import { getParticipantVolume } from '../lib/participantVolumes'; import { clearScreenShareVolumes, getScreenShareVolume, } from '../lib/screenShareVolumes'; import { startSoundboardHotkeys } from '../lib/soundboardHotkeys'; import { playEntry } from '../lib/soundboardPlayback'; import { getPrefs as getSoundboardPrefs, listSounds as listSoundboard, updatePrefs as updateSoundboardPrefs, } from '../lib/soundboardStorage'; import { type DisplaySurfaceHint, getPresetParams, getScreenShareSettings, type ScreenSharePreset, updateScreenShareSettings, } from '../lib/screenShareSettings'; import { devLocalSecretStore } from '../lib/secretStore'; import { supabase } from '../lib/supabase'; export type CallState = | { kind: 'idle' } | { kind: 'outgoing'; callId: string; conversationId: string; mediaKind: CallKind; ringingSince: string; } | { kind: 'incoming'; callId: string; conversationId: string; fromUserId: string; mediaKind: CallKind; } | { kind: 'connecting'; callId: string; conversationId: string; mediaKind: CallKind; } | { kind: 'connected'; callId: string; conversationId: string; mediaKind: CallKind; startedAt: string; } | { // LiveKit dropped the signaling socket but is actively retrying. The // room + tracks stay alive — the user's mic + speakers keep working — // they just can't reach peers until we're back. Distinct from // `connecting` so the UI can show "Verbinde neu…" vs "Verbinde…". kind: 'reconnecting'; callId: string; conversationId: string; mediaKind: CallKind; startedAt: string; } | { kind: 'error'; message: string }; export interface RemoteScreenShare { track: RemoteTrack; participantId: string; participantName: string; } // Visual call modes (Discord-style): grid shows all tiles equally, focus pins // one speaker with others in a strip, fullscreen is cinema mode. export type CallMode = 'grid' | 'focus' | 'fullscreen'; interface CallContextValue { state: CallState; room: Room | null; remoteParticipants: RemoteParticipant[]; isMuted: boolean; isE2EEActive: boolean; isScreenSharing: boolean; isCameraEnabled: boolean; isDeafened: boolean; /** Participants whose screen share the local user has actively clicked * "Bildschirm anschauen" on. Session-only (cleared on call end). Used to * gate both the