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,60 @@
# WebSocket Chat Monorepo
A real-time WebSocket chat application built with TypeScript, Express, and Vite.
## Project Structure
```
├── apps/
│ ├── server/ # Express WebSocket server
│ └── frontend/ # React/Vite frontend
├── packages/
│ └── shared-types/ # Shared TypeScript types
└── package.json # Root workspace configuration
```
## Quick Start
```bash
# Install all dependencies
npm install
# Start both server and frontend in development mode
npm start
# Or run individually:
npm run dev:server # Start server only
npm run dev:frontend # Start frontend only
```
## Available Scripts
- `npm start` - Build shared types and start both server and frontend
- `npm run dev:all` - Start both server and frontend concurrently
- `npm run dev:server` - Start server only
- `npm run dev:frontend` - Start frontend only
- `npm run build` - Build all packages
- `npm run typecheck` - Type check all packages
- `npm run lint` - Lint all packages
- `npm run clean` - Clean all build outputs
## Development
The monorepo uses npm workspaces for dependency management and TypeScript project references for type checking across packages.
### Server
- Runs on `http://localhost:3000`
- WebSocket endpoint: `ws://localhost:3000/ws`
- Health check: `http://localhost:3000/ping`
### Frontend
- Runs on `http://localhost:5173` (Vite default)
- Connects to WebSocket server for real-time messaging
## Features
- Real-time WebSocket communication
- Ping/pong latency measurement
- Shared TypeScript types across packages
- Hot reload for development
- Monorepo workspace management

View File

@@ -1,16 +1,19 @@
{
"name": "websocket",
"name": "@techniker-me/websocket-frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"typecheck": "tsc",
"clean": "rm -rf dist",
"lint": "echo 'No linting configured for frontend'"
},
"devDependencies": {
"typescript": "~5.8.3",
"vite": "^7.1.7"
"typescript": "5.9.2",
"vite": "7.1.7"
},
"dependencies": {
"@techniker-me/logger": "0.0.15",

View File

@@ -25,6 +25,11 @@ messagesElement.id = 'messages-container';
const messageInput = document.createElement('input');
messageInput.type = 'text';
messageInput.disabled = true;
messageInput.onkeydown = ({target}) => {
if (target.value === 'enter') {
sendMessage()
}
};
const actionButton = document.createElement('button');
actionButton.innerText = 'Set your username!';
@@ -32,8 +37,8 @@ actionButton.innerText = 'Set your username!';
disposables.add(websocket.on('message', (message: any) => {
if (message.pong) {
console.log('[WebSocket] Received message', message);
// Use server-calculated RTT
const rtt = message.pong.rtt || 0;
rttContainer.innerHTML = `RTT: [<span id="rtt-value">${rtt.toFixed(2)}</span>] ms`;
} else {
messagesElement.innerHTML += `<div class="message"> [${new Date(message.sentAt).toLocaleString("en-US", {timeStyle: 'short'})}]${message.message.author}: ${message.message.payload}</div>`;
@@ -50,9 +55,11 @@ chatContainer.append(actionButton);
appContainer.append(chatContainer);
actionButton.onclick = () => {
const usernamePrompt = prompt('Enter your name:');
if (!usernamePrompt) {
return;
}
username = usernamePrompt;
messageInput.disabled = false;
messageInput.placeholder = 'Enter a message...';
@@ -62,6 +69,7 @@ actionButton.onclick = () => {
function sendMessage() {
console.log('[actionButton] Set username');
if (!messageInput.value) {
return;
}

View File

@@ -1,4 +1,5 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
@@ -22,5 +23,8 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
"include": ["src"],
"references": [
{ "path": "../../packages/shared-types" }
]
}

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 680 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,15 +2,17 @@
"name": "@techniker-me/websocket-server",
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {
"preinstall": "npm prune",
"format": "prettier --write .",
"prelint": "npm install",
"lint": "eslint --max-warnings 0 .",
"prelint:fix": "npm run format",
"lint:fix": "eslint --fix .",
"dev": "tsx watch --clear-screen=false src/index.ts",
"typecheck": "tsc"
"build": "tsc",
"typecheck": "tsc",
"clean": "rm -rf dist"
},
"author": "Alexander Zinn",
"license": "ISC",
@@ -44,6 +46,7 @@
"@techniker-me/tools": "2025.0.16",
"body-parser": "2.2.0",
"cors": "2.8.5",
"express": "5.1.0",
"lru-cache": "11.2.2",
"moment": "2.30.1",
"morgan": "1.10.1",

View File

@@ -1,17 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "es2022",
"lib": ["es2022"],
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"moduleDetection": "force",
"module": "Preserve",
"resolveJsonModule": true,
"allowJs": false,
"esModuleInterop": true,
"isolatedModules": true,
"strict": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
@@ -20,9 +16,15 @@
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true
"noEmit": false,
"outDir": "./dist",
"rootDir": "./src",
"composite": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "dist"],
"references": [
{ "path": "../../packages/shared-types" }
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,26 @@
{
"dependencies": {
"body-parser": "^2.2.0",
"compression": "^1.8.1",
"express": "^5.1.0"
"name": "@techniker-me/websocket-chat",
"version": "0.0.0",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"build": "npm run build --workspaces",
"build:types": "npm run build -w @techniker-me/websocket-shared-types",
"dev": "npm run dev --workspaces --if-present",
"dev:server": "npm run dev -w @techniker-me/websocket-server",
"dev:frontend": "npm run dev -w @techniker-me/websocket-frontend",
"dev:all": "concurrently \"npm run dev:server\" \"npm run dev:frontend\"",
"clean": "npm run clean --workspaces --if-present",
"lint": "npm run lint --workspaces --if-present",
"typecheck": "npm run typecheck --workspaces --if-present",
"start": "npm run build:types && npm run dev:all"
},
"devDependencies": {
"@types/compression": "^1.8.1",
"@types/express": "^5.0.3"
}
"typescript": "5.9.2",
"concurrently": "8.2.2"
},
"author": "Alexander Zinn"
}

View File

@@ -0,0 +1,17 @@
{
"name": "@techniker-me/websocket-shared-types",
"version": "0.0.0",
"type": "module",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"clean": "rm -rf dist",
"typecheck": "tsc"
},
"devDependencies": {
"typescript": "~5.8.3"
},
"dependencies": {}
}

View File

@@ -0,0 +1,41 @@
// Shared types for WebSocket communication
export type Milliseconds = number;
export type Seconds = number;
export interface PingMessage {
ping: {
sentAt: number;
};
}
export interface PongMessage {
pong: {
sentAt: number;
rtt: number;
};
}
export interface ChatMessage {
message: {
author: string;
recipient: string;
payload: string;
};
}
export type WebSocketMessage = PingMessage | PongMessage | ChatMessage;
export enum WebSocketConnectionStatus {
Connecting = 'Connecting',
Open = 'Open',
Closed = 'Closed',
Error = 'Error'
}
export interface ExtendedWebSocket {
id: string;
remoteAddress: string;
isOpen(): boolean;
isClosed(): boolean;
}

View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"allowJs": true,
"strict": true,
"noEmit": false,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -1,3 +0,0 @@
save-exact=true
package-lock=false
@techniker-me:registry=https://registry-node.techniker.me

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"allowJs": true,
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"composite": true,
"incremental": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo"
},
"references": [
{ "path": "./packages/shared-types" },
{ "path": "./apps/server" },
{ "path": "./apps/frontend" }
],
"files": []
}