| 1 | /** |
| 2 | * Sounds and desktop notifications: what a person hears when chat moves, |
| 3 | * kept with their account by identity (`sound_settings` / |
| 4 | * `set_sound_settings`) so a phone, a laptop and the desktop app all sound |
| 5 | * the same. Which sound plays when is the web app's rule |
| 6 | * (apps/web/app/lib/chat-sounds.ts); identity keeps the choices. Do not |
| 7 | * disturb is not here: it is the person's presence (`dnd_until`, |
| 8 | * services/notify), which also silences pop-ups and pushes. |
| 9 | * |
| 10 | * On its own, with no imports, so node tests can load it. |
| 11 | */ |
| 12 | |
| 13 | /** |
| 14 | * The sounds, by what they are for. `message`: a new message in a |
| 15 | * conversation you have open but are not looking at. `direct`: a direct |
| 16 | * message. `mention`: you were @mentioned. `agent_done`: an agent finished |
| 17 | * something for you. `sent`: a soft tick as you send (off unless turned |
| 18 | * on). `call` is reserved for calls and has no setting yet. |
| 19 | */ |
| 20 | export type SoundCue = "message" | "direct" | "mention" | "agent_done" | "sent" | "call"; |
| 21 | |
| 22 | /** The cues a person can turn on and off, in the order the settings list them. */ |
| 23 | export const SOUND_CUES: readonly Exclude<SoundCue, "call">[] = ["message", "direct", "mention", "agent_done", "sent"]; |
| 24 | |
| 25 | /** A set of sounds: the same roles, a different character. */ |
| 26 | export type SoundSet = "soft" | "bright"; |
| 27 | |
| 28 | export const SOUND_SETS: readonly SoundSet[] = ["soft", "bright"]; |
| 29 | |
| 30 | export type SoundSettings = { |
| 31 | /** Whether anything plays at all. */ |
| 32 | sounds_enabled: boolean; |
| 33 | sound_set: SoundSet; |
| 34 | /** 0 to 100. */ |
| 35 | sound_volume: number; |
| 36 | /** Each cue on or off; a cue left out is on, except `sent`. */ |
| 37 | sound_cues: Partial<Record<SoundCue, boolean>>; |
| 38 | /** |
| 39 | * Whether an open tab shows a system notification (the browser's |
| 40 | * Notifications API) for a message to you while the window is not in |
| 41 | * front. Off unless turned on; the browser's permission is asked only |
| 42 | * from the settings page. |
| 43 | */ |
| 44 | desktop_toasts: boolean; |
| 45 | }; |
| 46 | |
| 47 | /** A change to them: only what is sent changes; `sound_cues` merges by cue. */ |
| 48 | export type SoundSettingsChange = Partial<Omit<SoundSettings, "sound_cues">> & { sound_cues?: Partial<Record<SoundCue, boolean>> }; |
| 49 | |
| 50 | /** Which cues play before anyone changed them: all but the tick as you send. */ |
| 51 | export const DEFAULT_SOUND_CUES: Record<Exclude<SoundCue, "call">, boolean> = { message: true, direct: true, mention: true, agent_done: true, sent: false }; |
| 52 | |
| 53 | export const DEFAULT_SOUND_SETTINGS: SoundSettings = { sounds_enabled: true, sound_set: "soft", sound_volume: 60, sound_cues: { ...DEFAULT_SOUND_CUES }, desktop_toasts: false }; |
| 54 | |
| 55 | /** Whether a cue plays under these settings: its own switch, else its default. */ |
| 56 | export function cueOn(settings: Pick<SoundSettings, "sound_cues">, cue: SoundCue): boolean { |
| 57 | const chosen = settings.sound_cues[cue]; |
| 58 | if (typeof chosen === "boolean") return chosen; |
| 59 | return cue === "call" ? true : DEFAULT_SOUND_CUES[cue]; |
| 60 | } |