basic chat
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
import {Subject, ReadOnlySubject, Disposable, DisposableList, EventPublisher} from '@techniker-me/tools';
|
||||
import type {Milliseconds} from '../../types/Units';
|
||||
import {WebSocketConnectionStatus} from './WebSocketConnectionStatus';
|
||||
|
||||
const pingIntervalDuration: Milliseconds = 2000;
|
||||
|
||||
export default class WebSocketConnection extends EventPublisher {
|
||||
private readonly _connectionDisposables = new DisposableList();
|
||||
private readonly _status: Subject<WebSocketConnectionStatus> = new Subject(WebSocketConnectionStatus.Closed);
|
||||
private readonly _readOnlyStatus: ReadOnlySubject<WebSocketConnectionStatus> = new ReadOnlySubject(this._status);
|
||||
private readonly _socket: WebSocket;
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
this._socket = new WebSocket(url);
|
||||
|
||||
this.initialize(this._socket);
|
||||
}
|
||||
|
||||
get status(): ReadOnlySubject<WebSocketConnectionStatus> {
|
||||
return this._readOnlyStatus;
|
||||
}
|
||||
|
||||
public on(event: string, handler: (...args: unknown[]) => void): Disposable {
|
||||
return super.subscribe(event, handler);
|
||||
}
|
||||
|
||||
public sendRequest<T>(name: string, payload?: T) {
|
||||
try {
|
||||
const payloadStringified = JSON.stringify({
|
||||
sentAt: Date.now(),
|
||||
[name]: payload
|
||||
});
|
||||
|
||||
this._socket.send(payloadStringified);
|
||||
} catch (error){
|
||||
console.error('[WebSocket] Error sending request [%o]', error);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._connectionDisposables.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private initialize(socket: WebSocket) {
|
||||
const websocketStart = performance.now();
|
||||
let pingIntervalId;
|
||||
|
||||
pingIntervalId = window.setInterval(() => {
|
||||
this.sendRequest('ping', {sentAt: Date.now()});
|
||||
}, pingIntervalDuration);
|
||||
|
||||
this.setStatus(WebSocketConnectionStatus.Connecting);
|
||||
|
||||
socket.onerror = error => {
|
||||
this.setStatus(WebSocketConnectionStatus.Error);
|
||||
window.clearInterval(pingIntervalId);
|
||||
console.error('[WebSocket] Error [%o]', error);
|
||||
};
|
||||
socket.onopen = () => {
|
||||
this.setStatus(WebSocketConnectionStatus.Open);
|
||||
|
||||
const websocketEnd = performance.now();
|
||||
|
||||
console.log(`[WebSocket] Connection time [${(websocketEnd - websocketStart)}] milliseconds`);
|
||||
};
|
||||
socket.onclose = () => {
|
||||
this.setStatus(WebSocketConnectionStatus.Closed);
|
||||
};
|
||||
socket.onmessage = (messageEvent) => {
|
||||
try {
|
||||
const messageData = JSON.parse(messageEvent.data);
|
||||
super.publish('message', messageData);
|
||||
} catch (error) {
|
||||
console.log('[WebSocket] Received non-JSON message [%s]', messageEvent.data);
|
||||
console.error('[WebSocket] Error parsing message [%o]', error);
|
||||
}
|
||||
};
|
||||
|
||||
this._connectionDisposables.add(new Disposable(() => {
|
||||
window.clearInterval(pingIntervalId);
|
||||
}));
|
||||
this._connectionDisposables.add(new Disposable(() => {
|
||||
socket.onerror = null;
|
||||
socket.onopen = null;
|
||||
socket.onclose = null;
|
||||
socket.onmessage = null;
|
||||
}));
|
||||
this._connectionDisposables.add(new Disposable(() => {
|
||||
// @ts-expect-error Disposing the subject
|
||||
this._status.value = null;
|
||||
}));
|
||||
this._connectionDisposables.add(new Disposable(socket.close.bind(socket)));
|
||||
|
||||
}
|
||||
|
||||
private setStatus(status: WebSocketConnectionStatus) {
|
||||
this._status.value = status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export enum WebSocketConnectionStatus {
|
||||
Connecting = 0,
|
||||
Open = 1,
|
||||
Closing = 2,
|
||||
Closed = 3,
|
||||
Error = 4
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user