Skip to content

Notification System

RTC Agent includes a lightweight notification system that intelligently triggers sound alerts, Toast popups, and icon animations based on user focus and window state.

NotificationController is the core of the notification system. It listens for message events and triggers notifications based on focus detection logic.

Rules:

ScenarioNotify?
User is in session A, session B receives message✅ Notify
User is in session A, session A receives message❌ Don’t notify
User is not viewing any session✅ Notify
User disabled notifications❌ Don’t notify
interface NotificationState {
unreadCount: number; // Unread notification count
lastNotificationAt: number | null; // Last notification timestamp
}
interface NotificationActions {
markAsRead(): void; // Mark all notifications as read
clearAll(): void; // Clear all notification state
}

Get notification state via NotificationContext:

import { consume } from '@lit/context';
import { NotificationContext, type NotificationContextValue } from '../contexts/notification.js';
@consume({ context: NotificationContext, subscribe: true })
@state()
private _notificationCtx: NotificationContextValue;
// Get unread count
const unread = this._notificationCtx.state.unreadCount;
// Mark as read
this._notificationCtx.actions.markAsRead();
SoundFileTriggerLoading Strategy
Message alertmessage.mp3New message receivedLoad immediately
Task completecomplete.mp3Task execution completedDeferred loading
Error alerterror.mp3Error occurredDeferred loading
// NotificationController._preloadSounds()
// Critical sound (message) loaded immediately
const soundUrls = { message: messageSoundUrl };
// Non-critical sounds loaded with delay (using requestIdleCallback)
const deferredSounds = {
complete: completeSoundUrl,
error: errorSoundUrl,
};

Control sound toggle through the settings panel:

// SettingsState.notifications.soundEnabled
interface NotificationSettings {
soundEnabled: boolean; // Whether sound is enabled
toastEnabled: boolean; // Whether Toast is enabled
}

Browsers require user interaction before playing audio. If sound is blocked, the system degrades gracefully:

sound.play().catch((error) => {
if (error.name === 'NotAllowedError') {
// Browser blocked autoplay, user interaction needed
console.warn('[NotificationController] Audio playback blocked by browser');
}
});

💡 Sound loading failure does not affect Toast and animation functionality.

Toast notifications support action buttons, allowing users to quickly navigate to the message source:

Toast Configuration:

// NotificationController._showToast()
this.toastController?.actions.show(
`${title}: ${content}`, // Notification content
'info', // Type
{
label: msg('View'), // Button text
onClick: () => this._navigateToSession(sessionId), // Click callback
}
);

After clicking the Toast “View” button:

  1. Switch to the target session (sessionController.actions.switchSession())
  2. Mark notification as read (actions.markAsRead())
  3. Dispatch rtc-notification-click event (carrying sessionId)

When the window is in minimized mode, new messages trigger the bubble icon animation:

Control animation via CSS data-notification attribute:

// NotificationController._animateMinimizeIcon()
this.host.setAttribute('data-notification', 'active');
// Animation persists until user expands window or clicks bubble
// Clear animation
this.host.removeAttribute('data-notification');
PropertyValue
ShapeRounded square (border-radius: 23.2%)
ColorOrange → Yellow pulse
AnimationContinuous pulse until user interaction

When restoring from minimized, notification state is automatically cleared:

// RtcAgent._handleModeTransition()
if (this._appliedMode === 'minimized' && mode !== 'minimized') {
this._notification.actions.markAsRead();
}

Triggered when user clicks the Toast “View” button:

interface NotificationClickDetail {
sessionId: string; // Target session ID
}
agent.addEventListener('rtc-notification-click', (e: CustomEvent<NotificationClickDetail>) => {
console.log('User navigated to session:', e.detail.sessionId);
});

The notification system supports full keyboard interaction:

KeyBehavior
TabSwitch focus between Toast buttons
Enter / SpaceActivate Toast action button
EscClose current Toast

Toast components use role="alert" and aria-live="polite" to ensure screen readers announce correctly.

To prevent UI lag from rapid consecutive messages, the notification system implements throttling:

private static readonly NOTIFY_THROTTLE_MS = 300;
private async _handleNewMessage(event: UIUpdateEvent) {
const now = Date.now();
if (now - this._lastNotifyTime < NotificationController.NOTIFY_THROTTLE_MS) {
return; // Throttled: skip
}
this._lastNotifyTime = now;
// ... trigger notification
}

Parameters:

ParameterValueDescription
NOTIFY_THROTTLE_MS300msMinimum interval between two notifications
const agent = document.querySelector('rtc-agent');
agent.addEventListener('rtc-agent-ready', () => {
// Configure notifications via SettingsController
// Note: Settings are typically adjusted by users in the settings panel
// This shows programmatic configuration
// Listen for notification events
agent.addEventListener('rtc-notification-click', (e) => {
console.log('Notification clicked:', e.detail.sessionId);
});
});
/* Hide Toast via CSS */
rtc-agent {
--rtc-toast-display: none;
}

Or toggle in the settings panel:

  • Turn off “Enable sound” → Disable sound effects
  • Turn off “Enable Toast” → Disable Toast popups