Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
325 lines
7.7 KiB
JavaScript
325 lines
7.7 KiB
JavaScript
/**
|
|
* 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,
|
|
};
|