bsonicdb
Version:
=========
364 lines (363 loc) • 14.4 kB
JavaScript
"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