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.
5
Web/WebSocket/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@techniker-me/websocket-chat",
|
||||
"version": "0.0.0",
|
||||
"workspaces": ["server", "frontend"]
|
||||
}
|
||||
60
Web/WebSocket/websocket/README.md
Normal 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
|
||||
@@ -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",
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 680 B After Width: | Height: | Size: 680 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -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",
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
1032
Web/WebSocket/websocket/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
17
Web/WebSocket/websocket/packages/shared-types/package.json
Normal 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": {}
|
||||
}
|
||||
41
Web/WebSocket/websocket/packages/shared-types/src/index.ts
Normal 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;
|
||||
}
|
||||
17
Web/WebSocket/websocket/packages/shared-types/tsconfig.json
Normal 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"]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
save-exact=true
|
||||
package-lock=false
|
||||
@techniker-me:registry=https://registry-node.techniker.me
|
||||
22
Web/WebSocket/websocket/tsconfig.json
Normal 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": []
|
||||
}
|
||||