mirror of
https://github.com/litruv/API-WebSocket-Bridge.git
synced 2026-07-24 02:36:03 +10:00
moved things
This commit is contained in:
130
source/.gitignore
vendored
Normal file
130
source/.gitignore
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
280
source/WebSocketDriver.js
Normal file
280
source/WebSocketDriver.js
Normal file
@@ -0,0 +1,280 @@
|
||||
const WebSocket = require('ws');
|
||||
const EventEmitter = require('events');
|
||||
const axios = require('axios');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* WebSocketDriver class for managing a WebSocket server and API updates.
|
||||
*/
|
||||
class WebSocketDriver {
|
||||
/**
|
||||
* Creates an instance of WebSocketDriver.
|
||||
* @param {Object} config - Configuration object for WebSocketDriver.
|
||||
*/
|
||||
constructor(config) {
|
||||
/**
|
||||
* Configuration object for WebSocketDriver.
|
||||
* @type {Object}
|
||||
*/
|
||||
this.config = config;
|
||||
this.config.events = require('./driverconfig.json').events;
|
||||
this.wss = null;
|
||||
this.eventEmitter = new EventEmitter();
|
||||
this.intervalId = null;
|
||||
this.previousData = {};
|
||||
this.validateConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the WebSocket server and API update interval.
|
||||
* @returns {Promise<void>} - A promise that resolves when the server is started.
|
||||
*/
|
||||
async start() {
|
||||
await this.checkSSLFilesExist();
|
||||
await this.checkAPIUpdates();
|
||||
this.startWebSocketServer();
|
||||
this.startAPIUpdateInterval();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the SSL certificate and key files exist and are readable.
|
||||
* @returns {Promise<void>} - A promise that resolves if the files exist and are readable, otherwise rejects with an error.
|
||||
* @throws {Error} - SSL certificate or key file not found or not readable.
|
||||
*/
|
||||
async checkSSLFilesExist() {
|
||||
try {
|
||||
await Promise.all([
|
||||
fs.promises.access(this.config.certPath, fs.constants.R_OK),
|
||||
fs.promises.access(this.config.keyPath, fs.constants.R_OK)
|
||||
]);
|
||||
} catch (error) {
|
||||
throw new Error('SSL certificate or key file not found or not readable');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for API updates and sets the initial data.
|
||||
* @returns {Promise<void>} - A promise that resolves when the API data is fetched and set.
|
||||
* @throws {Error} - Failed to fetch API data.
|
||||
*/
|
||||
async checkAPIUpdates() {
|
||||
try {
|
||||
const response = await axios.get(this.config.apiUrl);
|
||||
this.previousData = response.data;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to fetch API data: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the WebSocket server with SSL configuration.
|
||||
*/
|
||||
startWebSocketServer() {
|
||||
const serverConfig = {
|
||||
cert: fs.readFileSync(this.config.certPath),
|
||||
key: fs.readFileSync(this.config.keyPath),
|
||||
};
|
||||
const server = https.createServer(serverConfig);
|
||||
this.wss = new WebSocket.Server({ server });
|
||||
|
||||
this.wss.on('connection', this.handleConnection.bind(this));
|
||||
|
||||
server.listen(this.config.port, () => {
|
||||
this.log(`WebSocket server is running on port ${this.config.port}`, 'info');
|
||||
});
|
||||
|
||||
server.on('error', (error) => {
|
||||
this.log(`Server error: ${error.message}`, 'error');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a new WebSocket connection.
|
||||
* @param {WebSocket} ws - The WebSocket instance representing the connection.
|
||||
*/
|
||||
handleConnection(ws) {
|
||||
this.log('New WebSocket client connected', 'info');
|
||||
ws.send(JSON.stringify({ type: 'dataUpdate', data: this.previousData }));
|
||||
|
||||
ws.on('message', (message) => {
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(message);
|
||||
} catch (error) {
|
||||
this.log('Invalid JSON message received', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'subscribe':
|
||||
this.handleSubscription(ws, data.event);
|
||||
break;
|
||||
default:
|
||||
this.log(`Unknown message type: ${data.type}`, 'warn');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the API update interval.
|
||||
*/
|
||||
startAPIUpdateInterval() {
|
||||
this.intervalId = setInterval(async () => {
|
||||
try {
|
||||
const response = await axios.get(this.config.apiUrl);
|
||||
const newData = response.data;
|
||||
const updatedEvents = this.getUpdatedEvents(newData);
|
||||
updatedEvents.forEach((event) => {
|
||||
const eventData = this.getEventData(event, newData);
|
||||
this.eventEmitter.emit(event.emitEvent, eventData);
|
||||
});
|
||||
this.previousData = newData;
|
||||
} catch (error) {
|
||||
this.log(`Error updating API data: ${error.message}`, 'error');
|
||||
}
|
||||
}, this.config.updateInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the events that have updated data compared to the previous data.
|
||||
* @param {Object} newData - The new API data.
|
||||
* @returns {Array} - An array of event configurations that have updated data.
|
||||
*/
|
||||
getUpdatedEvents(newData) {
|
||||
return Object.entries(this.config.events)
|
||||
.filter(([event, config]) => {
|
||||
const { params } = config;
|
||||
return params.some((param) => newData[param] !== this.previousData[param]);
|
||||
})
|
||||
.map(([event]) => this.config.events[event]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the event data for a specific event from the API data.
|
||||
* @param {Object} event - The event configuration.
|
||||
* @param {Object} data - The API data.
|
||||
* @returns {Object} - The event data.
|
||||
*/
|
||||
getEventData(event, data) {
|
||||
const eventData = {};
|
||||
const { params } = event;
|
||||
params.forEach((param) => {
|
||||
const nestedParamValue = param.split('.').reduce((obj, key) => obj && obj[key], data);
|
||||
this.setNestedProperty(eventData, param, nestedParamValue);
|
||||
});
|
||||
return eventData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a nested property value in an object.
|
||||
* @param {Object} obj - The object to set the property value on.
|
||||
* @param {string} path - The dot-separated path of the property.
|
||||
* @param {*} value - The value to set.
|
||||
*/
|
||||
setNestedProperty(obj, path, value) {
|
||||
const keys = path.split('.');
|
||||
const lastKey = keys.pop();
|
||||
let currentObj = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
currentObj[key] = currentObj[key] || {};
|
||||
currentObj = currentObj[key];
|
||||
}
|
||||
|
||||
currentObj[lastKey] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a subscription request from a WebSocket client.
|
||||
* @param {WebSocket} ws - The WebSocket instance representing the connection.
|
||||
* @param {string} event - The event type to subscribe to.
|
||||
*/
|
||||
handleSubscription(ws, event) {
|
||||
if (event === 'all') {
|
||||
for (const eventConfig of Object.values(this.config.events)) {
|
||||
this.eventEmitter.on(eventConfig.emitEvent, (eventData) => {
|
||||
ws.send(JSON.stringify({ type: eventConfig.emitEvent, ...eventData }));
|
||||
});
|
||||
}
|
||||
this.log('Client subscribed to all events', 'info');
|
||||
} else {
|
||||
const eventConfig = this.config.events[event];
|
||||
if (!eventConfig) {
|
||||
this.log(`Unknown event type: ${event}`, 'warn');
|
||||
return;
|
||||
}
|
||||
this.eventEmitter.on(eventConfig.emitEvent, (eventData) => {
|
||||
ws.send(JSON.stringify({ type: eventConfig.emitEvent, ...eventData }));
|
||||
});
|
||||
this.log(`Client subscribed to event: ${event}`, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the WebSocket server and API update interval.
|
||||
*/
|
||||
stop() {
|
||||
clearInterval(this.intervalId);
|
||||
this.wss.close();
|
||||
this.log('WebSocket server stopped', 'info');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the configuration object.
|
||||
* @throws {Error} - If the configuration object is invalid or missing required properties.
|
||||
*/
|
||||
validateConfig() {
|
||||
const { certPath, keyPath, port, updateInterval, apiUrl, events } = this.config;
|
||||
|
||||
if (!certPath || typeof certPath !== 'string') {
|
||||
throw new Error('Invalid or missing certificate path in the configuration');
|
||||
}
|
||||
|
||||
if (!keyPath || typeof keyPath !== 'string') {
|
||||
throw new Error('Invalid or missing key path in the configuration');
|
||||
}
|
||||
|
||||
if (!port || typeof port !== 'number') {
|
||||
throw new Error('Invalid or missing port in the configuration');
|
||||
}
|
||||
|
||||
if (!updateInterval || typeof updateInterval !== 'number' || updateInterval <= 0) {
|
||||
throw new Error('Invalid or missing update interval in the configuration');
|
||||
}
|
||||
|
||||
if (!apiUrl || typeof apiUrl !== 'string') {
|
||||
throw new Error('Invalid or missing API URL in the configuration');
|
||||
}
|
||||
|
||||
if (!events || typeof events !== 'object' || Array.isArray(events)) {
|
||||
throw new Error('Invalid or missing events configuration in the configuration');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message with the specified log level.
|
||||
* @param {string} message - The message to log.
|
||||
* @param {string} [level='info'] - The log level (info, warn, error).
|
||||
*/
|
||||
log(message, level = 'info') {
|
||||
const logLevel = level.toLowerCase();
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
switch (logLevel) {
|
||||
case 'info':
|
||||
console.log(`[${timestamp}] INFO: ${message}`);
|
||||
break;
|
||||
case 'warn':
|
||||
console.log(`[${timestamp}] WARN: ${message}`);
|
||||
break;
|
||||
case 'error':
|
||||
console.error(`[${timestamp}] ERROR: ${message}`);
|
||||
break;
|
||||
default:
|
||||
console.log(`[${timestamp}] ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketDriver;
|
||||
7
source/config.json
Normal file
7
source/config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"certPath": "/etc/letsencrypt/live/salt.ruv.wtf/fullchain.pem",
|
||||
"keyPath": "/etc/letsencrypt/live/salt.ruv.wtf/privkey.pem",
|
||||
"port": 8080,
|
||||
"updateInterval": 5000,
|
||||
"apiUrl": "https://www.saltybet.com/state.json"
|
||||
}
|
||||
24
source/driverconfig.json
Normal file
24
source/driverconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"events": {
|
||||
"teamNamesChange": {
|
||||
"emitEvent": "teamNamesChange",
|
||||
"params": ["p1name", "p2name"]
|
||||
},
|
||||
"betAmountChange": {
|
||||
"emitEvent": "betAmountChange",
|
||||
"params": ["p1total", "p2total"]
|
||||
},
|
||||
"statusChange": {
|
||||
"emitEvent": "statusChange",
|
||||
"params": ["status"]
|
||||
},
|
||||
"alertChange": {
|
||||
"emitEvent": "alertChange",
|
||||
"params": ["alert"]
|
||||
},
|
||||
"remainingChange": {
|
||||
"emitEvent": "remainingChange",
|
||||
"params": ["remaining"]
|
||||
}
|
||||
}
|
||||
}
|
||||
16
source/index.js
Normal file
16
source/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const WebSocketDriver = require('./WebSocketDriver');
|
||||
|
||||
// Load the configuration from config.json
|
||||
const config = require('./config.json');
|
||||
|
||||
// Create an instance of WebSocketDriver
|
||||
const driver = new WebSocketDriver(config);
|
||||
|
||||
// Start the WebSocket driver
|
||||
driver.start()
|
||||
.then(() => {
|
||||
driver.log('WebSocket driver is running', 'info');
|
||||
})
|
||||
.catch((error) => {
|
||||
driver.log(`Failed to start WebSocket driver: ${error.message}`, 'error');
|
||||
});
|
||||
1731
source/package-lock.json
generated
Normal file
1731
source/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
source/package.json
Normal file
21
source/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "reporter",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.4.0",
|
||||
"events": "^3.3.0",
|
||||
"jquery": "^3.7.0",
|
||||
"socket.io": "^4.6.2",
|
||||
"socket.io-client": "^4.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esdoc": "^1.1.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user