UNPKG

bsonicdb

Version:

=========

364 lines (363 loc) 14.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BsonicDB = void 0; const create_js_1 = require("../Channels/create.cjs"); const ParamsError_js_1 = require("../../erros/ParamsError.cjs"); const express_1 = __importDefault(require("express")); const crypto = __importStar(require("crypto")); const ws_1 = require("ws"); const ws_2 = require("ws"); const eventsource_1 = __importDefault(require("eventsource")); class BsonicDB { app; wsServer; events; eventsService; subscribers; baseURL; authKey; authSecret; timeout; server; constructor({ port = 3000, host = "localhost", timeout = 5000, AuthKey = undefined, AuthSecret = undefined, httpOnly = true, }) { this.app = (0, express_1.default)(); this.wsServer = new ws_2.WebSocketServer({ noServer: true }); this.events = new Map(); this.eventsService = {}; this.subscribers = new Map(); this.baseURL = `${httpOnly ? 'http' : 'https'}://${host}:${port}`; this.authKey = AuthKey || crypto.randomBytes(32).toString('hex'); this.authSecret = AuthSecret || crypto.randomBytes(32).toString('hex'); this.timeout = timeout; this.initServer(port, host); } CreateChannel({ name, authKey, authSecret }) { if (!name) { throw new ParamsError_js_1.ParamsError("Name is Required"); } if (!this.validateAuth(authKey, authSecret)) { throw new ParamsError_js_1.ParamsError("Invalid authentication credentials"); } const channelId = name; const event = (0, create_js_1.CreateEvent)(); this.eventsService[channelId] = event; this.subscribers.set(channelId, new Set()); this.app.post(`/channels/${channelId}`, this.authMiddleware.bind(this), async (req, res) => { try { const content = req.body; const eventEmitter = this.eventsService[channelId]; if (eventEmitter) { eventEmitter.emit(channelId, content); await this.broadcastToSubscribers(channelId, content); res.status(200).json({ message: 'Event emitted successfully' }); } else { res.status(404).json({ message: 'Event not found' }); } } catch (error) { res.status(500).json({ message: 'Internal server error' }); } }); this.app.get(`/channels/${channelId}/subscribe`, this.authMiddleware.bind(this), (req, res) => { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('Access-Control-Allow-Origin', '*'); res.flushHeaders(); const subscriber = { send: (data) => { res.write(`data: ${JSON.stringify(data)}\n\n`); }, readyState: ws_1.WebSocket.OPEN, close: () => res.end() }; const subscribers = this.subscribers.get(channelId) || new Set(); subscribers.add(subscriber); this.subscribers.set(channelId, subscribers); req.on('close', () => { subscribers.delete(subscriber); }); }); const send = async (eventName, content) => { event.emit(eventName, content); await this.broadcastToSubscribers(channelId, content); }; const close = () => { delete this.eventsService[channelId]; const subscribers = this.subscribers.get(channelId); if (subscribers) { subscribers.forEach(subscriber => subscriber.close()); this.subscribers.delete(channelId); } }; return { event, send, close }; } Subscribe({ channelName, authKey, authSecret }) { if (!channelName) { throw new ParamsError_js_1.ParamsError("Channel name is required"); } if (!this.validateAuth(authKey, authSecret)) { throw new ParamsError_js_1.ParamsError("Invalid authentication credentials"); } const channelId = channelName; const event = (0, create_js_1.CreateEvent)(); return new Promise((resolve, reject) => { try { const eventSource = new eventsource_1.default(`${this.baseURL}/channels/${channelId}/subscribe`, { headers: { 'auth-key': authKey, 'auth-secret': authSecret }, withCredentials: true }); eventSource.onmessage = (e) => { const message = JSON.parse(e.data); event.emit(channelId, message); }; eventSource.onerror = (error) => { console.error(`EventSource error for channel ${channelId}:`, error); eventSource.close(); reject(new Error('Authentication failed or connection error')); }; eventSource.onopen = () => { resolve({ event, eventSource }); }; } catch (error) { reject(error); } }); } broadcastToSubscribers(channelId, message) { const subscribers = this.subscribers.get(channelId); console.log(`Broadcasting to channel ${channelId}, subscribers count: ${subscribers?.size || 0}`); if (subscribers) { subscribers.forEach(subscriber => { if (subscriber.readyState === ws_1.WebSocket.OPEN) { try { subscriber.send(message); console.log(`Message sent successfully to a subscriber on channel ${channelId}`); } catch (error) { console.error(`Error sending message to subscriber on channel ${channelId}:`, error); } } else { console.log(`Subscriber on channel ${channelId} not ready (state: ${subscriber.readyState})`); } }); } else { console.log(`No subscribers found for channel ${channelId}`); } } validateAuth(key, secret) { if (!key || !secret) return false; try { const keyBuffer = Buffer.from(key); const secretBuffer = Buffer.from(secret); const authKeyBuffer = Buffer.from(this.authKey); const authSecretBuffer = Buffer.from(this.authSecret); if (keyBuffer.length !== authKeyBuffer.length || secretBuffer.length !== authSecretBuffer.length) { return false; } return crypto.timingSafeEqual(keyBuffer, authKeyBuffer) && crypto.timingSafeEqual(secretBuffer, authSecretBuffer); } catch { return false; } } async initServer(port, host) { this.app.use(express_1.default.json()); this.setupRoutes(); this.server = this.app.listen(port, host, () => { console.log(`Server running at http://${host}:${port}/`); }); this.wsServer = new ws_2.WebSocketServer({ server: this.server }); this.wsServer.on('connection', this.handleWebSocketConnection.bind(this)); this.setupErrorHandlers(); } handleWebSocketConnection(ws, req) { const channelId = req.url.split('/subscribe/')[1]; if (!channelId) { ws.close(); return; } const subscribers = this.subscribers.get(channelId) || new Set(); subscribers.add(ws); this.subscribers.set(channelId, subscribers); ws.on('close', () => { subscribers.delete(ws); }); } authMiddleware(req, res, next) { const authKey = req.headers['auth-key']; const authSecret = req.headers['auth-secret']; if (this.validateAuth(authKey, authSecret)) { next(); } else { res.status(401).json({ message: 'Unauthorized' }); } } setupRoutes() { this.app.post('/set', this.authMiddleware.bind(this), this.asyncHandler(this.handleSet.bind(this))); this.app.get('/get/:key', this.authMiddleware.bind(this), this.asyncHandler(this.handleGet.bind(this))); this.app.delete('/delete/:key', this.authMiddleware.bind(this), this.asyncHandler(this.handleDelete.bind(this))); this.app.get('/exists/:key', this.authMiddleware.bind(this), this.asyncHandler(this.handleExists.bind(this))); this.app.get('/keys', this.authMiddleware.bind(this), this.asyncHandler(this.handleKeys.bind(this))); this.app.post('/clear', this.authMiddleware.bind(this), this.asyncHandler(this.handleClear.bind(this))); this.app.use((_, res) => { res.status(404).json({ message: 'Not Found' }); }); } setupErrorHandlers() { this.server.on('error', (error) => { console.error('Server error:', error); setTimeout(() => { this.server.close(); this.server.listen(this.server.address().port, this.server.address().address); }, 1000); }); process.on('uncaughtException', (error) => { console.error('Uncaught Exception:', error); }); process.on('SIGINT', () => { this.server.close(); process.exit(0); }); process.on('exit', () => { process.exit(0); }); process.on('unhandledRejection', (error) => { console.error('Unhandled Rejection:', error); }); } asyncHandler(fn) { return async (req, res, next) => { try { await fn(req, res, next); } catch (error) { res.status(500).json({ message: 'Internal Server Error' }); } }; } async handleSet(req, res) { const { key, value } = req.body; this.events.set(key, value); res.status(200).json({ message: 'Success' }); } async handleGet(req, res) { const { key } = req.params; const value = this.events.get(key); if (value === undefined) { res.status(404).json({ message: 'Not found' }); } else { res.status(200).json(value); } } async handleDelete(req, res) { const { key } = req.params; this.events.delete(key); res.status(200).json({ message: 'Success' }); } async handleExists(req, res) { const { key } = req.params; res.status(200).json(this.events.has(key)); } async handleKeys(_, res) { res.status(200).json(Array.from(this.events.keys())); } async handleClear(_, res) { this.events.clear(); res.status(200).json({ message: 'Success' }); } async set(key, value) { const response = await this.makeRequest('POST', '/set', { key, value }); if (!response.ok) throw new Error('Failed to set value'); } async get(key) { const response = await this.makeRequest('GET', `/get/${key}`); if (!response.ok) throw new Error('Failed to get value'); return response.json(); } async delete(key) { const response = await this.makeRequest('DELETE', `/delete/${key}`); if (!response.ok) throw new Error('Failed to delete value'); } async exists(key) { const response = await this.makeRequest('GET', `/exists/${key}`); if (!response.ok) throw new Error('Failed to check existence'); return response.json(); } async keys() { const response = await this.makeRequest('GET', '/keys'); if (!response.ok) throw new Error('Failed to get keys'); return response.json(); } async clear() { const response = await this.makeRequest('POST', '/clear'); if (!response.ok) throw new Error('Failed to clear database'); } async makeRequest(method, path, body) { return fetch(`${this.baseURL}${path}`, { method, headers: { 'Content-Type': 'application/json', 'Auth-Key': this.authKey, 'Auth-Secret': this.authSecret }, ...(body && { body: JSON.stringify(body) }), signal: AbortSignal.timeout(this.timeout) }); } } exports.BsonicDB = BsonicDB; //# sourceMappingURL=index.cjs.map