/** * DSS Notification Service * * Centralized messaging system with structured formats, error taxonomy, * and correlation IDs for enterprise-grade error tracking and user feedback. * * @module messaging */ // Event bus for pub/sub notifications const bus = new EventTarget(); // Event name constant export const NOTIFICATION_EVENT = 'dss-notification'; /** * Notification severity types */ export const NotificationType = { SUCCESS: 'success', ERROR: 'error', WARNING: 'warning', INFO: 'info', }; /** * Error taxonomy for structured error handling */ export const ErrorCode = { // User errors (E1xxx) USER_INPUT_INVALID: 'E1001', USER_ACTION_FORBIDDEN: 'E1002', USER_NOT_AUTHENTICATED: 'E1003', // Validation errors (E2xxx) VALIDATION_FAILED: 'E2001', VALIDATION_MISSING_FIELD: 'E2002', VALIDATION_INVALID_FORMAT: 'E2003', // API errors (E3xxx) API_REQUEST_FAILED: 'E3001', API_TIMEOUT: 'E3002', API_UNAUTHORIZED: 'E3003', API_NOT_FOUND: 'E3004', API_SERVER_ERROR: 'E3005', // System errors (E4xxx) SYSTEM_UNEXPECTED: 'E4001', SYSTEM_NETWORK: 'E4002', SYSTEM_STORAGE: 'E4003', // Integration errors (E5xxx) FIGMA_CONNECTION_FAILED: 'E5001', FIGMA_INVALID_KEY: 'E5002', FIGMA_API_ERROR: 'E5003', // Success codes (S1xxx) SUCCESS_OPERATION: 'S1001', SUCCESS_CREATED: 'S1002', SUCCESS_UPDATED: 'S1003', SUCCESS_DELETED: 'S1004', }; /** * Generate correlation ID for request tracking * @returns {string} UUID v4 correlation ID */ function generateCorrelationId() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * Message queue for persistence */ class MessageQueue { constructor(maxSize = 50) { this.maxSize = maxSize; this.storageKey = 'dss_message_queue'; } /** * Add message to queue * @param {Object} message - Notification message */ add(message) { try { const queue = this.getAll(); queue.unshift(message); // Keep only last maxSize messages const trimmed = queue.slice(0, this.maxSize); localStorage.setItem(this.storageKey, JSON.stringify(trimmed)); } catch (error) { console.warn('Failed to persist message to queue:', error); } } /** * Get all messages from queue * @returns {Array} Array of messages */ getAll() { try { const data = localStorage.getItem(this.storageKey); return data ? JSON.parse(data) : []; } catch (error) { console.warn('Failed to read message queue:', error); return []; } } /** * Clear the message queue */ clear() { try { localStorage.removeItem(this.storageKey); } catch (error) { console.warn('Failed to clear message queue:', error); } } /** * Get recent errors for debugging * @param {number} limit - Max number of errors to return * @returns {Array} Recent error messages */ getRecentErrors(limit = 10) { const queue = this.getAll(); return queue .filter(msg => msg.type === NotificationType.ERROR) .slice(0, limit); } } // Singleton message queue const messageQueue = new MessageQueue(); /** * Send a notification * * @param {Object} detail - Notification details * @param {string} detail.message - User-facing message * @param {NotificationType} [detail.type=INFO] - Notification type * @param {string} [detail.code] - Machine-readable error code * @param {Object} [detail.metadata] - Additional context for logging * @param {string} [detail.correlationId] - Optional correlation ID (auto-generated if not provided) * @param {number} [detail.duration] - Auto-dismiss duration in ms (0 = no auto-dismiss) * * @example * notify({ * message: 'Project created successfully', * type: NotificationType.SUCCESS, * code: ErrorCode.SUCCESS_CREATED, * metadata: { projectId: 'demo-ds' } * }); * * @example * notify({ * message: 'Failed to connect to Figma', * type: NotificationType.ERROR, * code: ErrorCode.FIGMA_CONNECTION_FAILED, * metadata: { fileKey: 'abc123', endpoint: '/figma/file' }, * correlationId: 'custom-id-123' * }); */ export function notify(detail) { const notification = { id: generateCorrelationId(), message: detail.message, type: detail.type || NotificationType.INFO, code: detail.code || null, metadata: detail.metadata || {}, correlationId: detail.correlationId || generateCorrelationId(), timestamp: new Date().toISOString(), duration: detail.duration !== undefined ? detail.duration : 5000, // Default 5s }; // Persist to queue messageQueue.add(notification); // Dispatch event bus.dispatchEvent(new CustomEvent(NOTIFICATION_EVENT, { detail: notification })); // Log for debugging const logMethod = notification.type === NotificationType.ERROR ? 'error' : 'log'; console[logMethod]('[DSS Notification]', { message: notification.message, code: notification.code, correlationId: notification.correlationId, metadata: notification.metadata, }); return notification; } /** * Subscribe to notifications * * @param {Function} callback - Called when notification is sent * @returns {Function} Unsubscribe function * * @example * const unsubscribe = subscribe((notification) => { * console.log('Notification:', notification.message); * }); * * // Later: unsubscribe(); */ export function subscribe(callback) { const handler = (event) => callback(event.detail); bus.addEventListener(NOTIFICATION_EVENT, handler); // Return unsubscribe function return () => { bus.removeEventListener(NOTIFICATION_EVENT, handler); }; } /** * Helper: Send success notification * @param {string} message - Success message * @param {string} [code] - Success code * @param {Object} [metadata] - Additional context */ export function notifySuccess(message, code = ErrorCode.SUCCESS_OPERATION, metadata = {}) { return notify({ message, type: NotificationType.SUCCESS, code, metadata, }); } /** * Helper: Send error notification * @param {string} message - Error message * @param {string} [code] - Error code * @param {Object} [metadata] - Additional context */ export function notifyError(message, code = ErrorCode.SYSTEM_UNEXPECTED, metadata = {}) { return notify({ message, type: NotificationType.ERROR, code, metadata, duration: 0, // Errors don't auto-dismiss }); } /** * Helper: Send warning notification * @param {string} message - Warning message * @param {Object} [metadata] - Additional context */ export function notifyWarning(message, metadata = {}) { return notify({ message, type: NotificationType.WARNING, metadata, duration: 7000, // Warnings stay a bit longer }); } /** * Helper: Send info notification * @param {string} message - Info message * @param {Object} [metadata] - Additional context */ export function notifyInfo(message, metadata = {}) { return notify({ message, type: NotificationType.INFO, metadata, }); } /** * Get message history * @returns {Array} All persisted messages */ export function getMessageHistory() { return messageQueue.getAll(); } /** * Get recent errors for debugging * @param {number} limit - Max number of errors * @returns {Array} Recent errors */ export function getRecentErrors(limit = 10) { return messageQueue.getRecentErrors(limit); } /** * Clear message history */ export function clearMessageHistory() { messageQueue.clear(); } // Export singleton queue for advanced use cases export { messageQueue }; export default { notify, subscribe, notifySuccess, notifyError, notifyWarning, notifyInfo, getMessageHistory, getRecentErrors, clearMessageHistory, NotificationType, ErrorCode, };