Break the server down in different files and change fetch all party lines behavior
This commit is contained in:
parent
cf70e11fe8
commit
f4258fb35f
8 changed files with 284 additions and 233 deletions
98
server/src/routes/connectionRoutes.ts
Normal file
98
server/src/routes/connectionRoutes.ts
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { Router } from 'express';
|
||||
import { query, validationResult } from 'express-validator';
|
||||
import { partyLines } from '../stores/dataStore';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Client } from "../types/partyLine";
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Route to verify if a party line exists before connecting
|
||||
router.get('/joinPartyLine', [
|
||||
query('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const currentPartyLine = req.query.partyLine;
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
res.status(200).send({ status: 'Connection to party line authorized' });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to join party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to connect to party line and receive rumors
|
||||
router.get('/connectPartyLine', [
|
||||
query('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const connectedPartyLine = req.query.partyLine;
|
||||
const partyLine = partyLines[connectedPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
|
||||
// Set headers for Server-Sent Events (SSE)
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const clientId = `${uuidv4()}`;
|
||||
const client: Client = { clientId, response: res, ipAddress: res.socket.remoteAddress };
|
||||
|
||||
// Add client to the party line
|
||||
partyLine.clients.push(client);
|
||||
partyLine.lastActivity = Date.now();
|
||||
|
||||
console.log(`JOIN: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
|
||||
// Send the last event to the client
|
||||
res.write(`data: ${partyLine.lastEvent}\n\n`);
|
||||
|
||||
// Keep-alive mechanism to keep the connection open
|
||||
const keepAliveId = setInterval(() => {
|
||||
res.write(': keep-alive\n\n');
|
||||
}, 15000);
|
||||
|
||||
// Function to remove the client from the party line
|
||||
const removeClient = () => {
|
||||
clearInterval(keepAliveId);
|
||||
const index = partyLine.clients.findIndex((client: any) => client.clientId === clientId);
|
||||
if (index !== -1) {
|
||||
partyLine.clients.splice(index, 1);
|
||||
console.log(`REMOVE: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
} else {
|
||||
console.log(`CLIENT_NOT_FOUND: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
}
|
||||
try {
|
||||
res.write('event: close\ndata: finished\n\n');
|
||||
res.end();
|
||||
} catch (err) {
|
||||
console.error(`ERROR: Error sending close event for client ${clientId}:`, err);
|
||||
}
|
||||
console.log(`DISCONNECT: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
};
|
||||
|
||||
// Handle client disconnection
|
||||
req.on('close', removeClient);
|
||||
req.on('error', (err: any) => {
|
||||
console.error(`ERROR: Error in client connection in party line ${connectedPartyLine}:`, err);
|
||||
removeClient();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to connect to party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
115
server/src/routes/managementRoutes.ts
Normal file
115
server/src/routes/managementRoutes.ts
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { Router } from 'express';
|
||||
import { body, validationResult } from 'express-validator';
|
||||
import { MAX_PARTY_LINES, INITIAL_RUMOR } from '../stores/configStore';
|
||||
import { partyLines } from '../stores/dataStore';
|
||||
import { broadcast } from '../services/broadcastService';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Route to get all party lines
|
||||
router.get('/partyLines', (_req: any, res: any) => {
|
||||
const allPartyLines = Object.keys(partyLines).map(key => ({
|
||||
name: key,
|
||||
...partyLines[key],
|
||||
clients: partyLines[key].clients.map(({ response, ...client }) => client)
|
||||
}));
|
||||
res.status(200).send(allPartyLines);
|
||||
});
|
||||
|
||||
// Route to create a new party line
|
||||
router.post('/createPartyLine', [
|
||||
body('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const { partyLine: currentPartyLine } = req.body;
|
||||
if (Object.keys(partyLines).length >= MAX_PARTY_LINES) {
|
||||
return res.status(400).send({ status: 'Maximum number of party lines reached' });
|
||||
}
|
||||
if (partyLines[currentPartyLine]) {
|
||||
return res.status(400).send({ status: 'Party line already exists' });
|
||||
}
|
||||
|
||||
// Create a new party line
|
||||
partyLines[currentPartyLine] = {
|
||||
clients: [],
|
||||
lastEvent: INITIAL_RUMOR,
|
||||
lastActivity: Date.now()
|
||||
};
|
||||
|
||||
console.log(`CREATE: { partyLine: ${currentPartyLine} }`);
|
||||
res.status(200).send({ status: 'Party line created', currentPartyLine });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to create party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to delete a party line
|
||||
router.delete('/deletePartyLine', [
|
||||
body('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const { partyLine: currentPartyLine } = req.body;
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
|
||||
// Broadcast deletion message to all clients
|
||||
broadcast(currentPartyLine, 'PARTY_LINE_DELETED');
|
||||
|
||||
// Disconnect all clients
|
||||
partyLine.clients.forEach((client: any) => {
|
||||
console.log(`DISCONNECTING: { partyLine: ${currentPartyLine}, clientId: ${client.clientId} }`);
|
||||
client.response.end();
|
||||
});
|
||||
|
||||
// Delete the party line
|
||||
delete partyLines[currentPartyLine];
|
||||
console.log(`DELETE: { partyLine: ${currentPartyLine} }`);
|
||||
res.status(200).send({ status: 'Party line deleted', currentPartyLine });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to delete party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to send an event to a party line
|
||||
router.post('/rumor', [
|
||||
body('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name'),
|
||||
body('rumor').isString().trim().escape().notEmpty().withMessage('Invalid rumor')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const { partyLine: connectedPartyLine, rumor } = req.body;
|
||||
const partyLine = partyLines[connectedPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
|
||||
console.log(`RECEIVE: { partyLine: ${connectedPartyLine}, rumor: ${rumor} }`);
|
||||
|
||||
// Update the last event and broadcast it to all clients
|
||||
partyLine.lastEvent = rumor;
|
||||
partyLine.clients.forEach((client: any) => {
|
||||
client.response.write(`data: ${rumor}\n\n`);
|
||||
});
|
||||
res.status(200).send({ status: 'Rumor broadcast' });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to send event', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
11
server/src/routes/navigationRoutes.ts
Normal file
11
server/src/routes/navigationRoutes.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Router } from 'express';
|
||||
import path from 'path';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Navigate to the admin route in the client
|
||||
router.get('/admin', (_req: any, res: any) => {
|
||||
res.sendFile(path.resolve(__dirname, '../../static', 'index.html'));
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -1,21 +1,21 @@
|
|||
import express from 'express';
|
||||
import { body, query, validationResult } from 'express-validator';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import cors from 'cors';
|
||||
import bodyParser from 'body-parser';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import dotenv from 'dotenv';
|
||||
import helmet from 'helmet';
|
||||
import path from 'path';
|
||||
|
||||
dotenv.config();
|
||||
import { partyLines } from './stores/dataStore';
|
||||
import { ENVIRONMENT, PORT, CLIENT_URL, DOCKER } from './stores/configStore';
|
||||
import { broadcast } from './services/broadcastService';
|
||||
import connectionRoutes from './routes/connectionRoutes';
|
||||
import managementRoutes from './routes/managementRoutes';
|
||||
import navigationRoutes from './routes/navigationRoutes';
|
||||
|
||||
const app = express();
|
||||
const PORT = Number(process.env.PORT);
|
||||
|
||||
// CORS configuration options
|
||||
const corsOptions = {
|
||||
origin: process.env.CLIENT_URL,
|
||||
origin: CLIENT_URL,
|
||||
optionsSuccessStatus: 200,
|
||||
methods: ['GET', 'POST', 'DELETE'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization']
|
||||
|
@ -38,7 +38,7 @@ app.use(limiter);
|
|||
app.use('/', express.static(path.join(__dirname, '../static')));
|
||||
|
||||
// Force HTTPS redirection and CSP in production
|
||||
if (process.env.ENVIRONMENT === 'production') {
|
||||
if (ENVIRONMENT === 'production') {
|
||||
app.use((req: any, res: any, next: any) => {
|
||||
if (req.headers['x-forwarded-proto'] !== 'https') {
|
||||
return res.redirect(`https://${req.headers.host}${req.url}`);
|
||||
|
@ -57,7 +57,7 @@ if (process.env.ENVIRONMENT === 'production') {
|
|||
})
|
||||
);
|
||||
} else {
|
||||
// Disable https preference for non-prod
|
||||
// Disable CSP for non-prod
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: false,
|
||||
|
@ -65,131 +65,17 @@ if (process.env.ENVIRONMENT === 'production') {
|
|||
);
|
||||
}
|
||||
|
||||
// Navigate to the admin route in the client
|
||||
app.get('/admin', (_req: any, res: any) => {
|
||||
res.sendFile(path.resolve(__dirname, '../static', 'index.html'));
|
||||
});
|
||||
// Routes
|
||||
app.use(navigationRoutes);
|
||||
app.use(managementRoutes);
|
||||
app.use(connectionRoutes);
|
||||
|
||||
// Maximum number of party lines allowed
|
||||
const MAX_PARTY_LINES = Number(process.env.MAX_PARTY_LINES);
|
||||
|
||||
// In-memory storage for party lines
|
||||
const partyLines = {};
|
||||
|
||||
// Function to broadcast a rumor to all clients at a party line
|
||||
const broadcast = (currentPartyLine: string, rumor: string) => {
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (partyLine) {
|
||||
console.log(`BROADCAST: { partyLine: ${currentPartyLine}, rumor: ${rumor} }`);
|
||||
partyLine.clients.forEach((client: any) => {
|
||||
if (client.response && typeof client.response.write === 'function') {
|
||||
try {
|
||||
client.response.write(`data: ${rumor}\n\n`);
|
||||
} catch (err) {
|
||||
console.error(`ERROR: Failed to write to client ${client.clientId}`, err);
|
||||
}
|
||||
} else {
|
||||
console.error(`ERROR: Response object is invalid for client ${client.clientId}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Route to verify if a party line exists before connecting
|
||||
app.get('/joinPartyLine', [
|
||||
query('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const currentPartyLine = req.query.partyLine;
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
res.status(200).send({ status: 'Connection to party line authorized' });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to join party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to connect to party line and receive rumors
|
||||
app.get('/connectPartyLine', [
|
||||
query('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const connectedPartyLine = req.query.partyLine;
|
||||
const partyLine = partyLines[connectedPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
|
||||
// Set headers for Server-Sent Events (SSE)
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const clientId = `${uuidv4()}`;
|
||||
const client = { clientId, response: res };
|
||||
|
||||
// Add client to the party line
|
||||
partyLine.clients.push(client);
|
||||
partyLine.lastActivity = Date.now();
|
||||
|
||||
console.log(`JOIN: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
|
||||
// Send the last event to the client
|
||||
res.write(`data: ${partyLine.lastEvent}\n\n`);
|
||||
|
||||
// Keep-alive mechanism to keep the connection open
|
||||
const keepAliveId = setInterval(() => {
|
||||
res.write(': keep-alive\n\n');
|
||||
}, 15000);
|
||||
|
||||
// Function to remove the client from the party line
|
||||
const removeClient = () => {
|
||||
clearInterval(keepAliveId);
|
||||
const index = partyLine.clients.findIndex((client: any) => client.clientId === clientId);
|
||||
if (index !== -1) {
|
||||
partyLine.clients.splice(index, 1);
|
||||
console.log(`REMOVE: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
} else {
|
||||
console.log(`CLIENT_NOT_FOUND: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
}
|
||||
try {
|
||||
res.write('event: close\ndata: finished\n\n');
|
||||
res.end();
|
||||
} catch (err) {
|
||||
console.error(`ERROR: Error sending close event for client ${clientId}:`, err);
|
||||
}
|
||||
console.log(`DISCONNECT: { partyLine: ${connectedPartyLine}, clientId: ${clientId} }`);
|
||||
};
|
||||
|
||||
// Handle client disconnection
|
||||
req.on('close', removeClient);
|
||||
req.on('error', (err: any) => {
|
||||
console.error(`ERROR: Error in client connection in party line ${connectedPartyLine}:`, err);
|
||||
removeClient();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to connect to party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Periodically resend the last event and clean up inactive party lines
|
||||
// Periodically resend the last rumor and clean up inactive party lines
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
Object.keys(partyLines).forEach((currentPartyLine) => {
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (partyLine.lastEvent) {
|
||||
if (partyLine.lastEvent && partyLine.clients.length > 0) {
|
||||
broadcast(currentPartyLine, partyLine.lastEvent);
|
||||
}
|
||||
if (now - partyLine.lastActivity > 3600000) { // 1 hour in milliseconds
|
||||
|
@ -199,113 +85,11 @@ setInterval(() => {
|
|||
});
|
||||
}, 30000);
|
||||
|
||||
// Route to create a new party line
|
||||
app.post('/createPartyLine', [
|
||||
body('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const { partyLine: currentPartyLine } = req.body;
|
||||
if (Object.keys(partyLines).length >= MAX_PARTY_LINES) {
|
||||
return res.status(400).send({ status: 'Maximum number of party lines reached' });
|
||||
}
|
||||
if (partyLines[currentPartyLine]) {
|
||||
return res.status(400).send({ status: 'Party line already exists' });
|
||||
}
|
||||
|
||||
// Create a new party line
|
||||
partyLines[currentPartyLine] = {
|
||||
clients: [],
|
||||
lastEvent: process.env.INITIAL_RUMOR,
|
||||
lastActivity: Date.now()
|
||||
};
|
||||
|
||||
console.log(`CREATE: { partyLine: ${currentPartyLine} }`);
|
||||
res.status(200).send({ status: 'Party line created', currentPartyLine });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to create party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to send an event to a party line
|
||||
app.post('/rumor', [
|
||||
body('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name'),
|
||||
body('rumor').isString().trim().escape().notEmpty().withMessage('Invalid rumor')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const { partyLine: connectedPartyLine, rumor } = req.body;
|
||||
const partyLine = partyLines[connectedPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
|
||||
console.log(`RECEIVE: { partyLine: ${connectedPartyLine}, rumor: ${rumor} }`);
|
||||
|
||||
// Update the last event and broadcast it to all clients
|
||||
partyLine.lastEvent = rumor;
|
||||
partyLine.clients.forEach((client: any) => {
|
||||
client.response.write(`data: ${rumor}\n\n`);
|
||||
});
|
||||
res.status(200).send({ status: 'Rumor broadcast' });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to send event', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Route to get all party lines
|
||||
app.get('/partyLines', (_req: any, res: any) => {
|
||||
const allPartyLines = Object.keys(partyLines);
|
||||
res.status(200).send({ allPartyLines });
|
||||
});
|
||||
|
||||
// Route to delete a party line
|
||||
app.delete('/deletePartyLine', [
|
||||
body('partyLine').isString().trim().escape().notEmpty().withMessage('Invalid party line name')
|
||||
], (req: any, res: any) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
try {
|
||||
const { partyLine: currentPartyLine } = req.body;
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (!partyLine) {
|
||||
return res.status(404).send({ status: 'Party line not found' });
|
||||
}
|
||||
|
||||
// Broadcast deletion message to all clients
|
||||
broadcast(currentPartyLine, 'PARTY_LINE_DELETED');
|
||||
|
||||
// Disconnect all clients
|
||||
partyLine.clients.forEach((client: any) => {
|
||||
console.log(`DISCONNECTING: { partyLine: ${currentPartyLine}, clientId: ${client.clientId} }`);
|
||||
client.response.end();
|
||||
});
|
||||
|
||||
// Delete the party line
|
||||
delete partyLines[currentPartyLine];
|
||||
console.log(`DELETE: { partyLine: ${currentPartyLine} }`);
|
||||
res.status(200).send({ status: 'Party line deleted', currentPartyLine });
|
||||
} catch (error) {
|
||||
console.error('ERROR: Failed to delete party line', error);
|
||||
res.status(500).send({ status: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Start the server
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Rumor Party Line`);
|
||||
console.log(`Internally running on http://localhost:${PORT} in ${process.env.ENVIRONMENT} mode.`);
|
||||
if (process.env.DOCKER) {
|
||||
console.log(`Internally running on http://localhost:${PORT} in ${ENVIRONMENT} mode.`);
|
||||
if (DOCKER) {
|
||||
console.log(`You're running on Docker!\n↳ Check your external port in the compose file to avoid confusion!`);
|
||||
}
|
||||
});
|
20
server/src/services/broadcastService.ts
Normal file
20
server/src/services/broadcastService.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { partyLines } from '../stores/dataStore';
|
||||
|
||||
// Function to broadcast a rumor to all clients at a party line
|
||||
export const broadcast = (currentPartyLine: string, rumor: string) => {
|
||||
const partyLine = partyLines[currentPartyLine];
|
||||
if (partyLine) {
|
||||
console.log(`BROADCAST: { partyLine: ${currentPartyLine}, rumor: ${rumor} }`);
|
||||
partyLine.clients.forEach((client: any) => {
|
||||
if (client.response && typeof client.response.write === 'function') {
|
||||
try {
|
||||
client.response.write(`data: ${rumor}\n\n`);
|
||||
} catch (err) {
|
||||
console.error(`ERROR: Failed to write to client ${client.clientId}`, err);
|
||||
}
|
||||
} else {
|
||||
console.error(`ERROR: Response object is invalid for client ${client.clientId}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
9
server/src/stores/configStore.ts
Normal file
9
server/src/stores/configStore.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
export const ENVIRONMENT = process.env.ENVIRONMENT || 'development';
|
||||
export const PORT = Number(process.env.PORT) || 3000;
|
||||
export const CLIENT_URL = process.env.CLIENT_URL || 'http://localhost:3000';
|
||||
export const MAX_PARTY_LINES = Number(process.env.MAX_PARTY_LINES) || 1;
|
||||
export const INITIAL_RUMOR = process.env.INITIAL_RUMOR || 'Lorem Ipsum.';
|
||||
export const DOCKER = Boolean(process.env.DOCKER);
|
3
server/src/stores/dataStore.ts
Normal file
3
server/src/stores/dataStore.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { PartyLine } from "../types/partyLine";
|
||||
|
||||
export const partyLines: PartyLine[] = [];
|
11
server/src/types/partyLine.ts
Normal file
11
server/src/types/partyLine.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export type PartyLine = {
|
||||
clients: Client[];
|
||||
lastActivity: number;
|
||||
lastEvent: string;
|
||||
}
|
||||
|
||||
export type Client = {
|
||||
clientId: string;
|
||||
response: any;
|
||||
ipAddress: string;
|
||||
}
|
Loading…
Add table
Reference in a new issue