Notifications Example

This example demonstrates how to build a comprehensive real-time notification system using WSX, including toast notifications, system alerts, and user-specific messaging.

Basic Notification System

HTML Structure

<!DOCTYPE html>
<html>
  <head>
    <title>WSX Notifications System</title>
    <script src="/wsx.js"></script>
    <style>
      body {
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          sans-serif;
        margin: 0;
        padding: 20px;
        background-color: #f5f5f5;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        display: grid;
        grid-template-columns: 1fr 300px;
        gap: 20px;
      }

      .main-content {
        background: white;
        padding: 30px;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
      }

      .notification-panel {
        background: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        height: fit-content;
      }

      /* Toast Notifications */
      .toast-container {
        position: fixed;
        top: 20px;
        right: 20px;
        z-index: 1000;
        display: flex;
        flex-direction: column;
        gap: 10px;
      }

      .toast {
        min-width: 300px;
        padding: 16px;
        border-radius: 6px;
        color: white;
        font-weight: 500;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
        transform: translateX(400px);
        opacity: 0;
        animation: slideIn 0.3s ease forwards;
        position: relative;
      }

      .toast.removing {
        animation: slideOut 0.3s ease forwards;
      }

      .toast-success {
        background: #10b981;
      }

      .toast-error {
        background: #ef4444;
      }

      .toast-warning {
        background: #f59e0b;
      }

      .toast-info {
        background: #3b82f6;
      }

      .toast-close {
        position: absolute;
        top: 8px;
        right: 8px;
        background: none;
        border: none;
        color: white;
        font-size: 18px;
        cursor: pointer;
        padding: 4px;
        border-radius: 4px;
      }

      .toast-close:hover {
        background: rgba(255, 255, 255, 0.2);
      }

      @keyframes slideIn {
        from {
          transform: translateX(400px);
          opacity: 0;
        }
        to {
          transform: translateX(0);
          opacity: 1;
        }
      }

      @keyframes slideOut {
        from {
          transform: translateX(0);
          opacity: 1;
        }
        to {
          transform: translateX(400px);
          opacity: 0;
        }
      }

      /* Notification Panel */
      .notification-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
      }

      .notification-badge {
        background: #ef4444;
        color: white;
        border-radius: 50%;
        padding: 4px 8px;
        font-size: 12px;
        font-weight: bold;
      }

      .notification-list {
        max-height: 400px;
        overflow-y: auto;
      }

      .notification-item {
        padding: 12px;
        border-bottom: 1px solid #e5e7eb;
        cursor: pointer;
        transition: background-color 0.2s;
      }

      .notification-item:hover {
        background-color: #f9fafb;
      }

      .notification-item.unread {
        background-color: #eff6ff;
        border-left: 4px solid #3b82f6;
      }

      .notification-item:last-child {
        border-bottom: none;
      }

      .notification-title {
        font-weight: 500;
        margin-bottom: 4px;
        color: #111827;
      }

      .notification-message {
        font-size: 14px;
        color: #6b7280;
        margin-bottom: 4px;
      }

      .notification-time {
        font-size: 12px;
        color: #9ca3af;
      }

      /* System Alerts */
      .system-alert {
        background: #fef3c7;
        border: 1px solid #f59e0b;
        border-radius: 6px;
        padding: 16px;
        margin-bottom: 20px;
        display: flex;
        align-items: center;
        gap: 12px;
      }

      .system-alert.error {
        background: #fee2e2;
        border-color: #ef4444;
      }

      .system-alert.success {
        background: #d1fae5;
        border-color: #10b981;
      }

      .system-alert.info {
        background: #dbeafe;
        border-color: #3b82f6;
      }

      .alert-icon {
        font-size: 20px;
      }

      .alert-content {
        flex: 1;
      }

      .alert-title {
        font-weight: 500;
        margin-bottom: 4px;
      }

      .alert-message {
        font-size: 14px;
        color: #6b7280;
      }

      .alert-close {
        background: none;
        border: none;
        font-size: 18px;
        cursor: pointer;
        padding: 4px;
        border-radius: 4px;
      }

      /* Controls */
      .controls {
        display: flex;
        gap: 10px;
        margin-bottom: 20px;
      }

      .btn {
        padding: 10px 16px;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        font-size: 14px;
        transition: background-color 0.2s;
      }

      .btn-primary {
        background: #3b82f6;
        color: white;
      }

      .btn-primary:hover {
        background: #2563eb;
      }

      .btn-secondary {
        background: #e5e7eb;
        color: #374151;
      }

      .btn-secondary:hover {
        background: #d1d5db;
      }

      .btn-success {
        background: #10b981;
        color: white;
      }

      .btn-success:hover {
        background: #059669;
      }

      .btn-danger {
        background: #ef4444;
        color: white;
      }

      .btn-danger:hover {
        background: #dc2626;
      }

      .btn-warning {
        background: #f59e0b;
        color: white;
      }

      .btn-warning:hover {
        background: #d97706;
      }
    </style>
  </head>
  <body>
    <div wx-config='{"url": "ws://localhost:3000/ws"}'>
      <!-- Toast Container -->
      <div id="toast-container" class="toast-container"></div>

      <!-- System Alerts -->
      <div id="system-alerts"></div>

      <div class="container">
        <div class="main-content">
          <h1>Notification System Demo</h1>

          <div class="controls">
            <button
              class="btn btn-success"
              wx-send="show-toast"
              wx-target="#toast-container"
              data-type="success"
            >
              Success Toast
            </button>
            <button
              class="btn btn-danger"
              wx-send="show-toast"
              wx-target="#toast-container"
              data-type="error"
            >
              Error Toast
            </button>
            <button
              class="btn btn-warning"
              wx-send="show-toast"
              wx-target="#toast-container"
              data-type="warning"
            >
              Warning Toast
            </button>
            <button
              class="btn btn-primary"
              wx-send="show-toast"
              wx-target="#toast-container"
              data-type="info"
            >
              Info Toast
            </button>
          </div>

          <div class="controls">
            <button
              class="btn btn-secondary"
              wx-send="show-system-alert"
              wx-target="#system-alerts"
              data-type="info"
            >
              System Alert
            </button>
            <button
              class="btn btn-secondary"
              wx-send="broadcast-notification"
              wx-target="#system-alerts"
            >
              Broadcast to All
            </button>
            <button
              class="btn btn-secondary"
              wx-send="send-private-message"
              wx-target="#system-alerts"
            >
              Send Private Message
            </button>
          </div>

          <h2>Activity Feed</h2>
          <div id="activity-feed">
            <p>Activity will appear here...</p>
          </div>
        </div>

        <div class="notification-panel">
          <div class="notification-header">
            <h3>Notifications</h3>
            <span
              id="notification-badge"
              class="notification-badge"
              style="display: none;"
              >0</span
            >
          </div>

          <div class="controls">
            <button
              class="btn btn-secondary"
              wx-send="mark-all-read"
              wx-target="#notification-list"
            >
              Mark All Read
            </button>
            <button
              class="btn btn-secondary"
              wx-send="clear-notifications"
              wx-target="#notification-list"
            >
              Clear All
            </button>
          </div>

          <div id="notification-list" class="notification-list">
            <!-- Notifications will be populated here -->
          </div>
        </div>
      </div>
    </div>

    <script>
      // Auto-remove toasts after 5 seconds
      function autoRemoveToast(toastElement) {
        setTimeout(() => {
          toastElement.classList.add("removing");
          setTimeout(() => {
            toastElement.remove();
          }, 300);
        }, 5000);
      }

      // Handle toast close buttons
      document.addEventListener("click", (e) => {
        if (e.target.classList.contains("toast-close")) {
          const toast = e.target.closest(".toast");
          toast.classList.add("removing");
          setTimeout(() => {
            toast.remove();
          }, 300);
        }
      });

      // Handle alert close buttons
      document.addEventListener("click", (e) => {
        if (e.target.classList.contains("alert-close")) {
          const alert = e.target.closest(".system-alert");
          alert.remove();
        }
      });

      // Auto-load notifications on page load
      window.addEventListener("load", () => {
        window.wsx.send("load-notifications", "#notification-list");
      });
    </script>
  </body>
</html>

Server Implementation

import { createExpressWSXServer } from "@wsx-sh/express";
import express from "express";

const wsx = createExpressWSXServer();
const app = wsx.getApp();

// Notification storage
const notifications = new Map(); // userId -> notifications array
const systemMessages = [];

// Mock user data
const users = [
  { id: 1, name: "John Doe", email: "john@example.com" },
  { id: 2, name: "Jane Smith", email: "jane@example.com" },
  { id: 3, name: "Bob Johnson", email: "bob@example.com" },
];

// Initialize mock notifications
function initMockNotifications() {
  users.forEach((user) => {
    notifications.set(user.id, [
      {
        id: Date.now() + Math.random(),
        title: "Welcome!",
        message: "Thanks for joining our platform",
        type: "info",
        read: false,
        timestamp: new Date(Date.now() - 3600000).toISOString(),
      },
      {
        id: Date.now() + Math.random(),
        title: "New Feature Available",
        message: "Check out our new dashboard widgets",
        type: "info",
        read: false,
        timestamp: new Date(Date.now() - 7200000).toISOString(),
      },
    ]);
  });
}

initMockNotifications();

// Show toast notification
wsx.on("show-toast", async (request, connection) => {
  const type = request.data.type || "info";
  const messages = {
    success: "Operation completed successfully!",
    error: "An error occurred. Please try again.",
    warning: "Please check your input and try again.",
    info: "This is an informational message.",
  };

  const toastId = Date.now() + Math.random();
  const message = messages[type];

  const toastHtml = `
    <div class="toast toast-${type}" data-toast-id="${toastId}">
      <div>${message}</div>
      <button class="toast-close">×</button>
    </div>
  `;

  return {
    id: request.id,
    target: request.target,
    html: toastHtml,
    swap: "beforeend",
  };
});

// Show system alert
wsx.on("show-system-alert", async (request, connection) => {
  const type = request.data.type || "info";
  const alerts = {
    info: {
      icon: "ℹ️",
      title: "System Information",
      message: "Scheduled maintenance tonight at 2 AM EST",
    },
    warning: {
      icon: "⚠️",
      title: "System Warning",
      message: "High server load detected",
    },
    error: {
      icon: "❌",
      title: "System Error",
      message: "Database connection temporarily unavailable",
    },
    success: {
      icon: "✅",
      title: "System Update",
      message: "All systems are operating normally",
    },
  };

  const alert = alerts[type];
  const alertHtml = `
    <div class="system-alert ${type}">
      <div class="alert-icon">${alert.icon}</div>
      <div class="alert-content">
        <div class="alert-title">${alert.title}</div>
        <div class="alert-message">${alert.message}</div>
      </div>
      <button class="alert-close">×</button>
    </div>
  `;

  return {
    id: request.id,
    target: request.target,
    html: alertHtml,
    swap: "beforeend",
  };
});

// Broadcast notification to all users
wsx.on("broadcast-notification", async (request, connection) => {
  const user = connection.sessionData?.user || { name: "Anonymous" };
  const message = {
    id: Date.now() + Math.random(),
    title: "Broadcast Message",
    message: `${user.name} sent a broadcast message to all users`,
    type: "info",
    read: false,
    timestamp: new Date().toISOString(),
  };

  // Add to all users' notifications
  users.forEach((user) => {
    const userNotifications = notifications.get(user.id) || [];
    userNotifications.unshift(message);
    notifications.set(user.id, userNotifications);
  });

  // Broadcast to all connected clients
  const connections = wsx.getConnections();
  connections.forEach((conn) => {
    // Send toast notification
    const toastHtml = `
      <div class="toast toast-info">
        <div>📢 ${message.message}</div>
        <button class="toast-close">×</button>
      </div>
    `;

    wsx.sendToConnection(conn.id, "#toast-container", toastHtml, "beforeend");

    // Update notification list
    const notificationHtml = generateNotificationHtml(message);
    wsx.sendToConnection(
      conn.id,
      "#notification-list",
      notificationHtml,
      "afterbegin"
    );
  });

  return {
    id: request.id,
    target: request.target,
    html: `
      <div class="system-alert success">
        <div class="alert-icon">✅</div>
        <div class="alert-content">
          <div class="alert-title">Broadcast Sent</div>
          <div class="alert-message">Message sent to all users</div>
        </div>
        <button class="alert-close">×</button>
      </div>
    `,
    swap: "beforeend",
  };
});

// Send private message
wsx.on("send-private-message", async (request, connection) => {
  const sender = connection.sessionData?.user || { name: "Anonymous" };
  const targetUser = users[Math.floor(Math.random() * users.length)];

  const message = {
    id: Date.now() + Math.random(),
    title: "Private Message",
    message: `${sender.name} sent you a private message`,
    type: "info",
    read: false,
    timestamp: new Date().toISOString(),
  };

  // Add to target user's notifications
  const userNotifications = notifications.get(targetUser.id) || [];
  userNotifications.unshift(message);
  notifications.set(targetUser.id, userNotifications);

  // Find target user's connection
  const targetConnection = wsx
    .getConnections()
    .find((conn) => conn.sessionData?.user?.id === targetUser.id);

  if (targetConnection) {
    // Send toast notification
    const toastHtml = `
      <div class="toast toast-info">
        <div>💬 ${message.message}</div>
        <button class="toast-close">×</button>
      </div>
    `;

    wsx.sendToConnection(
      targetConnection.id,
      "#toast-container",
      toastHtml,
      "beforeend"
    );

    // Update notification list
    const notificationHtml = generateNotificationHtml(message);
    wsx.sendToConnection(
      targetConnection.id,
      "#notification-list",
      notificationHtml,
      "afterbegin"
    );
  }

  return {
    id: request.id,
    target: request.target,
    html: `
      <div class="system-alert success">
        <div class="alert-icon">✅</div>
        <div class="alert-content">
          <div class="alert-title">Message Sent</div>
          <div class="alert-message">Private message sent to ${targetUser.name}</div>
        </div>
        <button class="alert-close">×</button>
      </div>
    `,
    swap: "beforeend",
  };
});

// Load notifications
wsx.on("load-notifications", async (request, connection) => {
  const user = connection.sessionData?.user || users[0]; // Default to first user for demo
  const userNotifications = notifications.get(user.id) || [];

  if (userNotifications.length === 0) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="notification-item">No notifications yet</div>`,
    };
  }

  const notificationsHtml = userNotifications
    .map(generateNotificationHtml)
    .join("");
  const unreadCount = userNotifications.filter((n) => !n.read).length;

  return [
    {
      id: request.id,
      target: request.target,
      html: notificationsHtml,
    },
    {
      id: request.id,
      target: "#notification-badge",
      html:
        unreadCount > 0
          ? `<span class="notification-badge">${unreadCount}</span>`
          : "",
      swap: "outerHTML",
    },
  ];
});

// Mark all notifications as read
wsx.on("mark-all-read", async (request, connection) => {
  const user = connection.sessionData?.user || users[0];
  const userNotifications = notifications.get(user.id) || [];

  userNotifications.forEach((notification) => {
    notification.read = true;
  });

  const notificationsHtml = userNotifications
    .map(generateNotificationHtml)
    .join("");

  return [
    {
      id: request.id,
      target: request.target,
      html: notificationsHtml,
    },
    {
      id: request.id,
      target: "#notification-badge",
      html: "",
      swap: "outerHTML",
    },
  ];
});

// Clear all notifications
wsx.on("clear-notifications", async (request, connection) => {
  const user = connection.sessionData?.user || users[0];
  notifications.set(user.id, []);

  return [
    {
      id: request.id,
      target: request.target,
      html: `<div class="notification-item">No notifications yet</div>`,
    },
    {
      id: request.id,
      target: "#notification-badge",
      html: "",
      swap: "outerHTML",
    },
  ];
});

// Helper function to generate notification HTML
function generateNotificationHtml(notification) {
  const timeAgo = formatTimeAgo(new Date(notification.timestamp));
  const unreadClass = notification.read ? "" : "unread";

  return `
    <div class="notification-item ${unreadClass}" data-notification-id="${notification.id}">
      <div class="notification-title">${notification.title}</div>
      <div class="notification-message">${notification.message}</div>
      <div class="notification-time">${timeAgo}</div>
    </div>
  `;
}

// Helper function to format time ago
function formatTimeAgo(date) {
  const now = new Date();
  const diffMs = now - date;
  const diffMins = Math.floor(diffMs / 60000);
  const diffHours = Math.floor(diffMins / 60);
  const diffDays = Math.floor(diffHours / 24);

  if (diffMins < 1) return "Just now";
  if (diffMins < 60) return `${diffMins}m ago`;
  if (diffHours < 24) return `${diffHours}h ago`;
  if (diffDays < 7) return `${diffDays}d ago`;
  return date.toLocaleDateString();
}

// Auto-generate random notifications every 30 seconds
setInterval(() => {
  const randomUser = users[Math.floor(Math.random() * users.length)];
  const notificationTypes = [
    {
      title: "New Order",
      message: "You have received a new order",
      type: "success",
    },
    {
      title: "System Update",
      message: "System has been updated",
      type: "info",
    },
    {
      title: "Payment Received",
      message: "Payment has been processed",
      type: "success",
    },
    {
      title: "Server Alert",
      message: "Server is experiencing high load",
      type: "warning",
    },
  ];

  const randomNotification =
    notificationTypes[Math.floor(Math.random() * notificationTypes.length)];
  const notification = {
    id: Date.now() + Math.random(),
    title: randomNotification.title,
    message: randomNotification.message,
    type: randomNotification.type,
    read: false,
    timestamp: new Date().toISOString(),
  };

  // Add to user's notifications
  const userNotifications = notifications.get(randomUser.id) || [];
  userNotifications.unshift(notification);
  notifications.set(randomUser.id, userNotifications);

  // Find user's connection and send notification
  const userConnection = wsx
    .getConnections()
    .find((conn) => conn.sessionData?.user?.id === randomUser.id);

  if (userConnection) {
    // Send toast notification
    const toastHtml = `
      <div class="toast toast-${notification.type}">
        <div><strong>${notification.title}</strong><br>${notification.message}</div>
        <button class="toast-close">×</button>
      </div>
    `;

    wsx.sendToConnection(
      userConnection.id,
      "#toast-container",
      toastHtml,
      "beforeend"
    );

    // Update notification list
    const notificationHtml = generateNotificationHtml(notification);
    wsx.sendToConnection(
      userConnection.id,
      "#notification-list",
      notificationHtml,
      "afterbegin"
    );

    // Update badge
    const unreadCount = userNotifications.filter((n) => !n.read).length;
    wsx.sendToConnection(
      userConnection.id,
      "#notification-badge",
      `<span class="notification-badge">${unreadCount}</span>`,
      "outerHTML"
    );
  }
}, 30000);

app.use(express.static("public"));
app.listen(3000, () => {
  console.log("Notifications server running on http://localhost:3000");
});

Advanced Notification Features

Push Notification Integration

// Push notification service
class PushNotificationService {
  constructor() {
    this.subscriptions = new Map();
  }

  subscribe(userId, subscription) {
    this.subscriptions.set(userId, subscription);
  }

  async sendPushNotification(userId, notification) {
    const subscription = this.subscriptions.get(userId);
    if (!subscription) return;

    try {
      await webpush.sendNotification(
        subscription,
        JSON.stringify({
          title: notification.title,
          body: notification.message,
          icon: "/icon-192x192.png",
          badge: "/badge-72x72.png",
          actions: [
            { action: "open", title: "Open" },
            { action: "close", title: "Close" },
          ],
        })
      );
    } catch (error) {
      console.error("Push notification failed:", error);
    }
  }
}

const pushService = new PushNotificationService();

// Subscribe to push notifications
wsx.on("subscribe-push", async (request, connection) => {
  const { subscription } = request.data;
  const user = connection.sessionData?.user;

  if (user && subscription) {
    pushService.subscribe(user.id, subscription);

    return {
      id: request.id,
      target: request.target,
      html: `<div class="success">Push notifications enabled</div>`,
    };
  }

  return {
    id: request.id,
    target: request.target,
    html: `<div class="error">Failed to enable push notifications</div>`,
  };
});

Notification Preferences

// Notification preferences
const userPreferences = new Map();

wsx.on("update-notification-preferences", async (request, connection) => {
  const user = connection.sessionData?.user;
  const { emailNotifications, pushNotifications, smsNotifications, types } =
    request.data;

  if (!user) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Please log in to update preferences</div>`,
    };
  }

  userPreferences.set(user.id, {
    email: emailNotifications,
    push: pushNotifications,
    sms: smsNotifications,
    types: types || [],
  });

  return {
    id: request.id,
    target: request.target,
    html: `<div class="success">Notification preferences updated</div>`,
  };
});

// Check if user should receive notification
function shouldReceiveNotification(userId, notificationType) {
  const preferences = userPreferences.get(userId);
  if (!preferences) return true; // Default to sending

  return preferences.types.includes(notificationType);
}

Email Notifications

import nodemailer from "nodemailer";

// Email service
const emailTransporter = nodemailer.createTransporter({
  host: process.env.SMTP_HOST,
  port: process.env.SMTP_PORT,
  secure: process.env.SMTP_SECURE === "true",
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

async function sendEmailNotification(user, notification) {
  const preferences = userPreferences.get(user.id);
  if (!preferences?.email) return;

  try {
    await emailTransporter.sendMail({
      from: process.env.FROM_EMAIL,
      to: user.email,
      subject: notification.title,
      html: `
        <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
          <h2>${notification.title}</h2>
          <p>${notification.message}</p>
          <div style="margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee;">
            <small style="color: #666;">
              You received this email because you have email notifications enabled.
              <a href="${process.env.APP_URL}/settings">Update your preferences</a>
            </small>
          </div>
        </div>
      `,
    });
  } catch (error) {
    console.error("Email notification failed:", error);
  }
}

Notification Templates

// Notification templates
const notificationTemplates = {
  welcome: {
    title: "Welcome to {{appName}}!",
    message: "Thank you for joining us, {{userName}}!",
    type: "success",
  },
  orderConfirmation: {
    title: "Order Confirmed",
    message: "Your order #{{orderNumber}} has been confirmed",
    type: "success",
  },
  paymentReceived: {
    title: "Payment Received",
    message: "We have received your payment of ${{amount}}",
    type: "success",
  },
  systemMaintenance: {
    title: "Scheduled Maintenance",
    message: "System maintenance is scheduled for {{date}} at {{time}}",
    type: "warning",
  },
  securityAlert: {
    title: "Security Alert",
    message: "Login detected from {{location}} at {{time}}",
    type: "warning",
  },
};

// Send templated notification
wsx.on("send-templated-notification", async (request, connection) => {
  const { templateId, variables, targetUserId } = request.data;
  const template = notificationTemplates[templateId];

  if (!template) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Template not found</div>`,
    };
  }

  // Replace template variables
  let title = template.title;
  let message = template.message;

  Object.entries(variables).forEach(([key, value]) => {
    title = title.replace(`{{${key}}}`, value);
    message = message.replace(`{{${key}}}`, value);
  });

  const notification = {
    id: Date.now() + Math.random(),
    title,
    message,
    type: template.type,
    read: false,
    timestamp: new Date().toISOString(),
  };

  // Send to specific user or all users
  const targetUsers = targetUserId
    ? [users.find((u) => u.id === targetUserId)]
    : users;

  targetUsers.forEach((user) => {
    if (!user) return;

    // Add to user's notifications
    const userNotifications = notifications.get(user.id) || [];
    userNotifications.unshift(notification);
    notifications.set(user.id, userNotifications);

    // Send to connected user
    const userConnection = wsx
      .getConnections()
      .find((conn) => conn.sessionData?.user?.id === user.id);

    if (userConnection) {
      const toastHtml = `
        <div class="toast toast-${notification.type}">
          <div><strong>${notification.title}</strong><br>${notification.message}</div>
          <button class="toast-close">×</button>
        </div>
      `;

      wsx.sendToConnection(
        userConnection.id,
        "#toast-container",
        toastHtml,
        "beforeend"
      );
    }
  });

  return {
    id: request.id,
    target: request.target,
    html: `<div class="success">Notification sent using template: ${templateId}</div>`,
  };
});

Notification Analytics

// Track notification analytics
const notificationAnalytics = {
  sent: new Map(),
  opened: new Map(),
  clicked: new Map(),
};

wsx.on("track-notification-event", async (request, connection) => {
  const { notificationId, event, data } = request.data;

  switch (event) {
    case "sent":
      notificationAnalytics.sent.set(notificationId, {
        timestamp: Date.now(),
        userId: connection.sessionData?.user?.id,
      });
      break;

    case "opened":
      notificationAnalytics.opened.set(notificationId, {
        timestamp: Date.now(),
        userId: connection.sessionData?.user?.id,
      });
      break;

    case "clicked":
      notificationAnalytics.clicked.set(notificationId, {
        timestamp: Date.now(),
        userId: connection.sessionData?.user?.id,
        action: data.action,
      });
      break;
  }

  return {
    id: request.id,
    target: request.target,
    html: "", // No visual feedback needed
  };
});

// Get notification analytics
wsx.on("get-notification-analytics", async (request, connection) => {
  const user = connection.sessionData?.user;

  if (!user?.isAdmin) {
    return {
      id: request.id,
      target: request.target,
      html: `<div class="error">Admin access required</div>`,
    };
  }

  const totalSent = notificationAnalytics.sent.size;
  const totalOpened = notificationAnalytics.opened.size;
  const totalClicked = notificationAnalytics.clicked.size;

  const openRate =
    totalSent > 0 ? ((totalOpened / totalSent) * 100).toFixed(1) : 0;
  const clickRate =
    totalOpened > 0 ? ((totalClicked / totalOpened) * 100).toFixed(1) : 0;

  const analyticsHtml = `
    <div class="notification-analytics">
      <h3>Notification Analytics</h3>
      <div class="stats-grid">
        <div class="stat">
          <div class="stat-value">${totalSent}</div>
          <div class="stat-label">Total Sent</div>
        </div>
        <div class="stat">
          <div class="stat-value">${totalOpened}</div>
          <div class="stat-label">Opened</div>
        </div>
        <div class="stat">
          <div class="stat-value">${totalClicked}</div>
          <div class="stat-label">Clicked</div>
        </div>
        <div class="stat">
          <div class="stat-value">${openRate}%</div>
          <div class="stat-label">Open Rate</div>
        </div>
        <div class="stat">
          <div class="stat-value">${clickRate}%</div>
          <div class="stat-label">Click Rate</div>
        </div>
      </div>
    </div>
  `;

  return {
    id: request.id,
    target: request.target,
    html: analyticsHtml,
  };
});
This comprehensive notifications example demonstrates how to build a full-featured real-time notification system with WSX, including toast notifications, system alerts, push notifications, email integration, templates, and analytics.

Best Practices

  1. User Experience: Use appropriate notification types and timing
  2. Performance: Batch notifications and use efficient data structures
  3. Permissions: Respect user preferences and privacy settings
  4. Accessibility: Ensure notifications are accessible to all users
  5. Rate Limiting: Prevent notification spam
  6. Persistence: Store notifications for offline users
  7. Analytics: Track notification performance and user engagement

Next Steps