@raphab3/hermes-notifier
Version:
JavaScript/React plugin for Hermes notifications system with SSE support
244 lines (240 loc) • 8.58 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* Hermes Notifier - JavaScript/React Plugin
*
* A JavaScript library for integrating Hermes notifications into web applications
* using Server-Sent Events (SSE) for real-time notifications.
*/
/**
* Main HermesNotifier class for managing SSE connections and notifications
*/
class HermesNotifier {
constructor(config) {
this.eventSource = null;
this.notificationHandlers = new Set();
this.connectionHandlers = new Set();
this.errorHandlers = new Set();
this.reconnectAttempts = 0;
this.reconnectTimeout = null;
this.connectionStatus = { connected: false, reconnecting: false };
this.config = {
reconnectDelay: 5000,
maxReconnectAttempts: 10,
...config,
};
}
/**
* Connect to the Hermes notifications SSE stream
*/
async connect() {
if (this.eventSource) {
this.disconnect();
}
try {
const sseUrl = `${this.config.baseUrl}/sse/notifications/${encodeURIComponent(this.config.userId)}/`;
this.eventSource = new EventSource(sseUrl);
this.eventSource.onopen = () => {
this.reconnectAttempts = 0;
this.updateConnectionStatus({ connected: true, reconnecting: false });
};
this.eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// Handle different message types
if (data.type === 'notification' || data.type === 'new_notification') {
const notification = data.notification || data;
this.notifyHandlers(notification);
}
else if (data.type === 'heartbeat') {
// Heartbeat - connection is alive
console.debug('Hermes: Heartbeat received');
}
}
catch (error) {
console.warn('Hermes: Failed to parse notification data:', error);
}
};
this.eventSource.onerror = (event) => {
console.error('Hermes: SSE connection error:', event);
this.updateConnectionStatus({
connected: false,
reconnecting: this.reconnectAttempts < this.config.maxReconnectAttempts,
error: 'Connection error'
});
this.handleConnectionError();
};
}
catch (error) {
this.handleError(new Error(`Failed to connect: ${error}`));
}
}
/**
* Disconnect from the SSE stream
*/
disconnect() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
this.reconnectTimeout = null;
}
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
this.updateConnectionStatus({ connected: false, reconnecting: false });
}
/**
* Check if currently connected
*/
isConnected() {
return this.connectionStatus.connected;
}
/**
* Get current connection status
*/
getConnectionStatus() {
return { ...this.connectionStatus };
}
/**
* Subscribe to notification events
*/
onNotification(handler) {
this.notificationHandlers.add(handler);
return () => this.notificationHandlers.delete(handler);
}
/**
* Subscribe to connection status changes
*/
onConnectionChange(handler) {
this.connectionHandlers.add(handler);
return () => this.connectionHandlers.delete(handler);
}
/**
* Subscribe to error events
*/
onError(handler) {
this.errorHandlers.add(handler);
return () => this.errorHandlers.delete(handler);
}
/**
* Send a test notification (for development)
*/
async sendTestNotification(title, body) {
try {
const response = await fetch(`${this.config.baseUrl}/api/v1/notifications/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.config.token}`,
},
body: JSON.stringify({
user_id: this.config.userId,
title,
body,
source_system: 'hermes-notifier-plugin',
priority: 'normal',
channels: ['in_app'],
}),
});
if (!response.ok) {
throw new Error(`Failed to send test notification: ${response.statusText}`);
}
}
catch (error) {
this.handleError(new Error(`Failed to send test notification: ${error}`));
}
}
/**
* Mark a notification as read
*/
async markAsRead(notificationId) {
try {
const response = await fetch(`${this.config.baseUrl}/api/v1/notifications/${notificationId}/mark_as_read/`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.config.token}`,
},
});
if (!response.ok) {
throw new Error(`Failed to mark notification as read: ${response.statusText}`);
}
}
catch (error) {
this.handleError(new Error(`Failed to mark notification as read: ${error}`));
}
}
/**
* Get unread notification count
*/
async getUnreadCount() {
try {
const response = await fetch(`${this.config.baseUrl}/api/v1/notifications/unread_count/?user_id=${encodeURIComponent(this.config.userId)}`, {
headers: {
'Authorization': `Bearer ${this.config.token}`,
},
});
if (!response.ok) {
throw new Error(`Failed to get unread count: ${response.statusText}`);
}
const data = await response.json();
return data.unread_count || 0;
}
catch (error) {
this.handleError(new Error(`Failed to get unread count: ${error}`));
return 0;
}
}
notifyHandlers(notification) {
this.notificationHandlers.forEach(handler => {
try {
handler(notification);
}
catch (error) {
console.error('Hermes: Error in notification handler:', error);
}
});
}
updateConnectionStatus(status) {
this.connectionStatus = { ...this.connectionStatus, ...status };
this.connectionHandlers.forEach(handler => {
try {
handler(this.connectionStatus);
}
catch (error) {
console.error('Hermes: Error in connection handler:', error);
}
});
}
handleConnectionError() {
if (this.reconnectAttempts < this.config.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Hermes: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
this.reconnectTimeout = window.setTimeout(() => {
this.connect();
}, delay);
}
else {
this.handleError(new Error('Max reconnection attempts reached'));
this.updateConnectionStatus({
connected: false,
reconnecting: false,
error: 'Max reconnection attempts reached'
});
}
}
handleError(error) {
console.error('Hermes:', error);
this.errorHandlers.forEach(handler => {
try {
handler(error);
}
catch (handlerError) {
console.error('Hermes: Error in error handler:', handlerError);
}
});
}
}
exports.HermesNotifier = HermesNotifier;
exports.default = HermesNotifier;
//# sourceMappingURL=index.js.map