Add README and initial project structure for WebSocket chat application

* Created README.md for project overview and setup instructions.
* Updated App component to use selector for todo items.
* Enhanced TodoItemComponent styling and structure.
* Introduced new Redux selectors for better state management.
* Added initial configuration files for RequireJS and Bun.
* Established project structure for WebSocket chat application with server and frontend components.
* Included necessary dependencies and configurations for TypeScript and Vite.
This commit is contained in:
2025-09-30 03:19:52 -04:00
parent 0cc0ce13e7
commit 0345f3d2d0
71 changed files with 996 additions and 1065 deletions

View File

@@ -0,0 +1,3 @@
{
"idf.pythonInstallPath": "/opt/homebrew/bin/python3"
}

View File

@@ -1,5 +1,6 @@
import {LoggerFactory} from '@techniker-me/logger';
import WebSocketServerFactory from './net/websockets/WebSocketServerFactory';
import type { WebSocketHandler } from 'bun';
LoggerFactory.setLoggingLevel('All');
@@ -15,7 +16,7 @@ const server = Bun.serve({
return new Response('hi');
},
websocket: webSocketRelayServer
websocket: webSocketRelayServer as WebSocketHandler<undefined>
});
process.on('SIGINT', () => {

View File

@@ -95,4 +95,102 @@ export default class WebSocketServer {
get perMessageDeflate(): PerMessageDeflate {
return this._perMessageDeflate;
}
public start(connectDelegate: (req, websocket, headers) => void, requestDelegate: (req, websocket, headers) => void, disconnectDelegate: (req, websocket, headers) => void, pongDelegate: (req, websocket, headers) => void) {
if (this._server) {
throw new Error('Can only start server once');
}
assert.assertFunction('connectDelegate', connectDelegate);
assert.assertFunction('requestDelegate', requestDelegate);
assert.assertFunction('disconnectDelegate', disconnectDelegate);
assert.assertFunction('pongDelegate', pongDelegate);
const serverOptions = _.cloneDeep(this._parameters);
log.info('Websocket server listening on port [%s] bound to [%s]', this._httpServer.address().port, _.get(serverOptions, ['path']));
serverOptions.noServer = true;
this._server = new ws.Server(serverOptions);
this._extensions = new WebsocketExtensions();
this._extensions.add(deflate);
// TODO Prevent upgrade request from being handled by this WS server if it's not within the configured path
this._server._server = this._httpServer;
this._httpServer.on('listening', this._server.emit.bind(this._server, 'listening'));
this._httpServer.on('error', this._server.emit.bind(this._server, 'error'));
this._httpServer.on('upgrade', (req, socket, head) => {
if (!_.startsWith(req.url, serverOptions.path)) {
return;
}
this._server.handleUpgrade(req, socket, head, ws => {
this._server.emit('connection', ws, req);
});
});
this._server.on('error', e => {
log.error('An error occurred on websocket', e);
});
this._server.on('connection', (connection, req) => {
let closed = false;
try {
connection.id = randomstring.generate(connectionIdLength);
connection.remoteAddress = getRemoteAddress.call(this, connection, req);
log.debug('[%s] connected from [%s] with headers [%j]', connection.id, connection.remoteAddress, req.headers);
connection.isOpen = () => connection.readyState === ws.OPEN;
connection.isClosed = () => connection.readyState === ws.CLOSED;
connection.on('error', e => {
log.error('An error occurred on websocket', e);
});
connection.on('message', message => {
try {
requestDelegate(connection, message);
} catch (e) {
log.error('Request handler failed for message [%s]', message, e);
}
});
connection.on('close', (reasonCode, description) => {
if (closed) {
log.warn('[%s] Multiple close events [%s] [%s] [%s]', connection.id, connection.remoteAddress, reasonCode, description);
return;
}
closed = true;
try {
disconnectDelegate(connection, reasonCode, description);
} catch (e) {
log.error('Disconnect handler failed', e);
}
});
connection.on('pong', message => {
try {
pongDelegate(connection, message);
} catch (e) {
log.error('Pong handler failed', e);
}
});
return connectDelegate(connection, _.get(req, ['headers']));
} catch (e) {
log.error('Accept/connect handler failed', e);
}
}
);
return this;
}
}
}

View File

@@ -9,11 +9,13 @@ export default class WebSocketServerFactory {
const webSocketRelayServerOptions: WebSocketServerOptions = {
onSocketError: (client, error) => logger.error(`Error: [%o] [${error.message}]`, client),
onSocketOpen: client => {
console.log('New WebSocketClient [%o]', client);
logger.debug('New WebSocketClient [%o]', client);
clients.add(client);
},
onSocketMessage: (fromClient, message) => {
console.log('Relaying message [%o]', message);
logger.debug(`Relaying message [%o]`, message);
for (const client of clients) {
@@ -24,7 +26,10 @@ export default class WebSocketServerFactory {
client.send(message);
}
},
onSocketClose: client => clients.delete(client),
onSocketClose: client => {
console.log('Client closed [%o]', client);
clients.delete(client);
},
onSocketDrain: client => logger.debug('Client drain [%o]', client),
publishToSelf: false
};