diff --git a/app/auth/token_store.py b/app/auth/token_store.py index 3a640e9..0cf4e49 100644 --- a/app/auth/token_store.py +++ b/app/auth/token_store.py @@ -105,6 +105,26 @@ async def list_refresh_sessions_for_user(*, user_id: int) -> list[RefreshSession try: redis = get_redis_client() session_ids = await redis.smembers(f"auth:user_refresh:{user_id}") + if not session_ids: + cursor = 0 + discovered: list[str] = [] + while True: + cursor, keys = await redis.scan(cursor=cursor, match="auth:refresh:*", count=200) + for raw_key in keys: + key = raw_key.decode("utf-8") if isinstance(raw_key, bytes) else str(raw_key) + if not key.startswith("auth:refresh:"): + continue + jti = key.removeprefix("auth:refresh:") + if not jti: + continue + owner = await redis.get(key) + if owner and str(owner).isdigit() and int(owner) == user_id: + discovered.append(jti) + if cursor == 0: + break + if discovered: + await redis.sadd(f"auth:user_refresh:{user_id}", *discovered) + session_ids = {item.encode("utf-8") for item in discovered} sessions: list[RefreshSession] = [] stale: list[str] = [] for raw_jti in session_ids: diff --git a/web/src/components/MessageList.tsx b/web/src/components/MessageList.tsx index e6eb4df..d6291cf 100644 --- a/web/src/components/MessageList.tsx +++ b/web/src/components/MessageList.tsx @@ -956,88 +956,43 @@ async function downloadFileFromUrl(url: string): Promise { } function AudioInlinePlayer({ src, title }: { src: string; title: string }) { - const audioRef = useRef(null); - const activate = useAudioPlayerStore((s) => s.activate); - const detach = useAudioPlayerStore((s) => s.detach); - const setPlayingState = useAudioPlayerStore((s) => s.setPlaying); - const setVolumeState = useAudioPlayerStore((s) => s.setVolume); - const [isPlaying, setIsPlaying] = useState(false); - const [duration, setDuration] = useState(0); - const [position, setPosition] = useState(0); - const [volume, setVolume] = useState(1); + const track = useAudioPlayerStore((s) => s.track); + const isPlayingGlobal = useAudioPlayerStore((s) => s.isPlaying); + const durationGlobal = useAudioPlayerStore((s) => s.duration); + const positionGlobal = useAudioPlayerStore((s) => s.position); + const volumeGlobal = useAudioPlayerStore((s) => s.volume); + const playTrack = useAudioPlayerStore((s) => s.playTrack); + const togglePlayGlobal = useAudioPlayerStore((s) => s.togglePlay); + const seekToGlobal = useAudioPlayerStore((s) => s.seekTo); + const setVolumeGlobal = useAudioPlayerStore((s) => s.setVolume); - useEffect(() => { - const audio = audioRef.current; - if (!audio) return; - - const onLoaded = () => { - setDuration(Number.isFinite(audio.duration) ? audio.duration : 0); - }; - const onTime = () => { - setPosition(audio.currentTime || 0); - }; - const onEnded = () => { - setIsPlaying(false); - setPlayingState(audio, false); - }; - const onPlay = () => { - activate(audio, { src, title }); - setPlayingState(audio, true); - }; - const onPause = () => { - setPlayingState(audio, false); - }; - - audio.addEventListener("loadedmetadata", onLoaded); - audio.addEventListener("timeupdate", onTime); - audio.addEventListener("ended", onEnded); - audio.addEventListener("play", onPlay); - audio.addEventListener("pause", onPause); - return () => { - audio.removeEventListener("loadedmetadata", onLoaded); - audio.removeEventListener("timeupdate", onTime); - audio.removeEventListener("ended", onEnded); - audio.removeEventListener("play", onPlay); - audio.removeEventListener("pause", onPause); - detach(audio); - }; - }, [activate, detach, setPlayingState, src, title]); + const isActiveTrack = track?.src === src; + const isPlaying = isActiveTrack && isPlayingGlobal; + const duration = isActiveTrack ? durationGlobal : 0; + const position = isActiveTrack ? positionGlobal : 0; + const volume = volumeGlobal; async function togglePlay() { - const audio = audioRef.current; - if (!audio) return; - if (isPlaying) { - audio.pause(); - setIsPlaying(false); + if (isActiveTrack) { + await togglePlayGlobal(); return; } - try { - activate(audio, { src, title }); - await audio.play(); - setIsPlaying(true); - } catch { - setIsPlaying(false); - } + await playTrack({ src, title }); } function onSeek(nextValue: number) { - const audio = audioRef.current; - if (!audio) return; - audio.currentTime = nextValue; - setPosition(nextValue); + if (!isActiveTrack) { + return; + } + seekToGlobal(nextValue); } function onVolume(nextValue: number) { - const audio = audioRef.current; - if (!audio) return; - audio.volume = nextValue; - setVolume(nextValue); - setVolumeState(audio, nextValue); + setVolumeGlobal(nextValue); } return (
-