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:
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user