Entidadactivecreado Wed May 20#chat#workspace#web-audio#notifications#favicon#typescript#cloudflare#workers
chatNotify — Librería de notificaciones del chat
Módulo TypeScript en src/lib/chatNotify.ts que encapsula los tres mecanismos de aviso visual y sonoro del widget TeamChat cuando llegan mensajes nuevos y el usuario no está mirando el tab.
Exporta tres funciones puras sin estado de React — pueden ser llamadas desde cualquier componente o hook del workspace.
API pública
playChatBeep(): void
Genera dos tonos cortos (“ding-dong”) usando la Web Audio API (AudioContext):
| Tono | Frecuencia | Inicio | Duración |
|---|---|---|---|
| Tono 1 | 880 Hz | now | 200 ms |
| Tono 2 | 1100 Hz | now + 120ms | 200 ms |
- Volumen rampeado (attack 10 ms, decay exponencial) para evitar clicks.
- La instancia de
AudioContextse crea lazy (singleton) y se reutiliza. - Soporta el prefijo
webkitAudioContext(Safari legacy). - No-op en SSR (
typeof window === 'undefined').
setTabBadge(count: number): void
Modifica document.title para mostrar (N) Título original cuando count > 0, o restaurar el título original cuando count === 0.
- El título original se guarda en
window['__ws_original_title__']la primera vez (limpiando cualquier badge previo). - No-op en SSR.
setFaviconBadge(active: boolean): void
Dibuja un punto rojo (círculo #ef4444 con borde blanco de 2px) en la esquina superior derecha del favicon original, usando un <canvas> 32×32.
- Si
active = false: restaura elhreforiginal del<link rel="icon">. - Si no existe
<link rel="icon">en el<head>, lo crea. - Si la imagen del favicon no carga (CORS u otro error), el fallback es un círculo rojo sólido de 32×32.
- El favicon modificado se inyecta como
data:image/png(base64). - No-op en SSR.
Diseño
- Sin dependencias externas — solo Web APIs nativas (
AudioContext,Canvas,document). - Sin estado de módulo salvo el singleton
audioCtxyoriginalFaviconHref(strings/null), que sobreviven entre renders pero se inicializan lazy. - Idempotente: llamar
setTabBadge(0)osetFaviconBadge(false)cuando ya están limpios es seguro. - El limpiado se hace en el
useEffectde cleanup deTeamChatpara no dejar el título con badge si el componente se desmonta.
Uso en TeamChat.tsx
import { playChatBeep, setFaviconBadge, setTabBadge } from '../../lib/chatNotify';
// Cuando llega mensaje nuevo y tab oculta:
playChatBeep();
setTabBadge(newCount);
setFaviconBadge(true);
// Al marcar como leído (focus / visibilitychange):
setTabBadge(0);
setFaviconBadge(false);
// Cleanup al desmontar:
useEffect(() => () => { setTabBadge(0); setFaviconBadge(false); }, []);
Véase también
- [[feature—chat—avisos-maximize-paste-jitsi]]
- [[entity—chat—widget—teamchat]]
- [[entity—chat—endpoint—upload-image]]
- [[entity—chat—endpoint—chat-images]]
- [[workspace—dashboard]]
Referenciado desde
- Chat del equipo — Avisos audio+badge, maximizar, paste imágenes y Jitsi (PR#58)
- Chat equipo: avisos audio+badge, maximizar, paste imágenes y Jitsi Meet
- GET /api/chat-images/:name — CF Pages Function para servir imágenes del chat desde R2
- POST /api/chat/upload-image — CF Pages Function para subir imágenes al chat