import { IPushRule, IPushRules, MatrixClient } from 'matrix-js-sdk'; import { AccountDataEvent } from '../../../types/matrix/accountData'; import { getAccountData } from '../../utils/room'; import { getNotificationMode, NotificationMode } from '../../hooks/useNotificationMode'; import { RoomNotificationMode, setRoomNotificationPreference, } from '../../hooks/useRoomsNotificationPreferences'; // localStorage key for timed mute timers export const MUTE_TIMERS_KEY = 'io.lotus.mute_timers'; // setTimeout's delay is a signed 32-bit int; larger values overflow and fire // immediately. Clamp long delays to this max (~24.8 days). export const MAX_MUTE_TIMEOUT_MS = 2_147_483_647; export type MuteTimerEntry = { roomId: string; unmuteAt: number }; export function loadMuteTimers(): MuteTimerEntry[] { try { const parsed = JSON.parse(localStorage.getItem(MUTE_TIMERS_KEY) ?? '[]'); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } export function saveMuteTimers(timers: MuteTimerEntry[]): void { localStorage.setItem(MUTE_TIMERS_KEY, JSON.stringify(timers)); } // Pure decision for the unmute guard: a timed mute should only be reset back to // Unset if the room's notification mode is still Mute at expiry time. If the user // manually changed it (e.g. to All messages) while the timer was pending, leave // their choice alone — just let the stale timer entry get dropped. export function shouldResetMuteOnUnmute(currentMode: RoomNotificationMode): boolean { return currentMode === RoomNotificationMode.Mute; } // Reads the room's live notification mode straight from account data push rules, // mirroring useRoomsNotificationPreferences' per-room derivation, without needing // the React hook (this runs from plain timers/effects, not components). export function getLiveRoomNotificationMode( mx: MatrixClient, roomId: string, ): RoomNotificationMode { const pushRules = getAccountData(mx, AccountDataEvent.PushRules)?.getContent(); const global = pushRules?.global; const overrideRule = global?.override?.find((rule: IPushRule) => rule.rule_id === roomId); if (overrideRule && getNotificationMode(overrideRule.actions) === NotificationMode.OFF) { return RoomNotificationMode.Mute; } const roomRule = global?.room?.find((rule: IPushRule) => rule.rule_id === roomId); if (roomRule) { return getNotificationMode(roomRule.actions) === NotificationMode.OFF ? RoomNotificationMode.SpecialMessages : RoomNotificationMode.AllMessages; } return RoomNotificationMode.Unset; } // Reverse a timed mute: restore the room's notification mode to Unset and drop // its persisted timer. Shared by the in-session timer and the boot-time restore. // Only resets the mode if it is still Mute — otherwise a manual change made // during the mute window (e.g. switching to "All messages") would silently get // reverted when the stale timer fires. export async function unmuteRoom(mx: MatrixClient, roomId: string): Promise { const currentMode = getLiveRoomNotificationMode(mx, roomId); if (shouldResetMuteOnUnmute(currentMode)) { await setRoomNotificationPreference( mx, roomId, RoomNotificationMode.Unset, RoomNotificationMode.Mute, ).catch(() => {}); } saveMuteTimers(loadMuteTimers().filter((e) => e.roomId !== roomId)); } export function scheduleMuteTimer(roomId: string, durationMs: number, onUnmute: () => void): void { const unmuteAt = Date.now() + durationMs; const existing = loadMuteTimers().filter((e) => e.roomId !== roomId); saveMuteTimers([...existing, { roomId, unmuteAt }]); setTimeout(onUnmute, Math.min(durationMs, MAX_MUTE_TIMEOUT_MS)); }