Введение в WebSocket и TypeScript для real-time приложений
Современные веб-приложения всё чаще требуют мгновенного обмена данными между клиентом и сервером. Традиционный HTTP-протокол, работающий по принципу «запрос-ответ», не подходит для задач, где критична скорость доставки информации: онлайн-чаты, биржевые котировки, многопользовательские игры или системы мониторинга. Именно здесь на помощь приходит WebSocket — протокол, обеспечивающий постоянное двустороннее соединение.
В сочетании с TypeScript — строго типизированным надмножеством JavaScript — разработка WebSocket-приложений становится не только эффективной, но и безопасной. TypeScript позволяет выявлять ошибки на этапе компиляции, улучшает читаемость кода и упрощает рефакторинг. В этой статье мы разберём полный цикл создания real-time приложения на WebSocket с TypeScript: от теории до практических примеров.
Основы WebSocket: как работает протокол
Что такое WebSocket?
WebSocket (RFC 6455) — это протокол, который устанавливает постоянное соединение между клиентом (браузером) и сервером. После рукопожатия (handshake) данные могут передаваться в обоих направлениях без повторных HTTP-запросов. Это снижает задержки и нагрузку на сеть.
Отличие от HTTP
- HTTP: клиент инициирует запрос → сервер отвечает → соединение закрывается (или остаётся открытым для следующих запросов, но всё равно инициируется клиентом).
- WebSocket: после установки соединения сервер может отправлять данные клиенту в любой момент (push-уведомления).
Жизненный цикл WebSocket-соединения
- Рукопожатие (Handshake): клиент отправляет HTTP-запрос с заголовком
Upgrade: websocket. Сервер отвечает кодом 101 Switching Protocols. - Обмен данными: после успешного рукопожатия соединение переключается на WebSocket-протокол. Данные передаются в виде фреймов.
- Закрытие: любая из сторон может инициировать закрытие соединения, отправив специальный управляющий фрейм.
Создание WebSocket-сервера на TypeScript
Выбор библиотеки
Для серверной части мы будем использовать популярную библиотеку ws — легковесную и быструю реализацию WebSocket для Node.js. Установим её вместе с типами для TypeScript:
npm install ws
npm install -D @types/ws
Базовый сервер
Создадим файл server.ts:
import { WebSocketServer, WebSocket } from 'ws';
const PORT = 8080;
const wss = new WebSocketServer({ port: PORT });
console.log(`WebSocket сервер запущен на ws://localhost:${PORT}`);
wss.on('connection', (ws: WebSocket) => {
console.log('Новое подключение');
// Отправляем приветственное сообщение
ws.send('Добро пожаловать на сервер!');
// Обработка входящих сообщений
ws.on('message', (message: Buffer) => {
const text = message.toString();
console.log(`Получено: ${text}`);
// Эхо-ответ
ws.send(`Эхо: ${text}`);
});
// Обработка закрытия соединения
ws.on('close', () => {
console.log('Клиент отключился');
});
// Обработка ошибок
ws.on('error', (error: Error) => {
console.error('Ошибка WebSocket:', error);
});
});
Обработка разных типов сообщений
В реальных приложениях сообщения часто структурированы в формате JSON. Создадим типизированный обработчик:
interface ChatMessage {
type: 'message' | 'join' | 'leave';
username: string;
content?: string;
timestamp: number;
}
wss.on('connection', (ws: WebSocket) => {
ws.on('message', (data: Buffer) => {
try {
const msg: ChatMessage = JSON.parse(data.toString());
switch (msg.type) {
case 'join':
console.log(`${msg.username} присоединился к чату`);
broadcast({ type: 'system', content: `${msg.username} вошёл в чат` });
break;
case 'message':
broadcast(msg);
break;
default:
ws.send(JSON.stringify({ error: 'Неизвестный тип сообщения' }));
}
} catch (e) {
ws.send(JSON.stringify({ error: 'Некорректный JSON' }));
}
});
});
function broadcast(message: object): void {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
}
Клиентская часть: WebSocket в браузере на TypeScript
Подключение к серверу
Создадим файл client.ts для браузерного окружения:
const WS_URL = 'ws://localhost:8080';
let socket: WebSocket;
function connect(): void {
socket = new WebSocket(WS_URL);
socket.onopen = () => {
console.log('Соединение установлено');
// Отправляем сообщение о входе
sendMessage({ type: 'join', username: 'User123', timestamp: Date.now() });
};
socket.onmessage = (event: MessageEvent) => {
const data = JSON.parse(event.data);
console.log('Получено сообщение:', data);
// Здесь можно обновить UI
};
socket.onclose = () => {
console.log('Соединение закрыто');
// Попробовать переподключиться через 3 секунды
setTimeout(connect, 3000);
};
socket.onerror = (error: Event) => {
console.error('Ошибка WebSocket:', error);
};
}
function sendMessage(msg: object): void {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(msg));
} else {
console.warn('Соединение не открыто');
}
}
connect();
Типизация событий
Для улучшения типизации создадим обёртку:
type MessageHandler = (data: ChatMessage) => void;
class TypedWebSocket {
private ws: WebSocket;
constructor(url: string) {
this.ws = new WebSocket(url);
}
onMessage(handler: MessageHandler): void {
this.ws.onmessage = (event) => {
const data: ChatMessage = JSON.parse(event.data);
handler(data);
};
}
sendMessage(msg: ChatMessage): void {
this.ws.send(JSON.stringify(msg));
}
close(): void {
this.ws.close();
}
}
// Использование
const client = new TypedWebSocket('ws://localhost:8080');
client.onMessage((