All files / common websocket.gateway.ts

95.74% Statements 45/47
80% Branches 4/5
92.85% Functions 13/14
97.72% Lines 43/44

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 977x             7x 7x 7x   7x       7x 35x   7x     35x 35x 35x     19x   19x 19x 19x       19x 19x 19x     19x       3x 3x 4x 3x 3x       3x 3x 3x 3x         13x   13x 11x 2x 2x           8x 4x 4x 4x         1x 2x         24x       3x     7x   1x      
import {
  MessageBody, OnGatewayConnection, OnGatewayDisconnect,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
  WsResponse
} from '@nestjs/websockets';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Server, WebSocket } from 'ws';
import { IncomingMessage } from 'http';
import { Logger } from '@nestjs/common';
import { BroadcastingEvent } from './interfaces';
 
@WebSocketGateway({ path: '/ws' })
export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnect {
  private readonly logger = new Logger(WebsocketGateway.name);
 
  @WebSocketServer()
  private server!: Server; // magically injected
 
  private clients: { [token: string] : WebSocket } = {};
  private clientsCount$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
  private clientLost$: Subject<string> = new Subject<string>();
 
  handleConnection(client: WebSocket, message: IncomingMessage): void {
    const token = WebsocketGateway.getTokenFromUrl(message.url as string);
 
    this.clients[token] = client;
    this.clientsCount$.next(Object.values(this.clients).length);
    this.logger.log(`client connected: ${token}`);
  }
 
  static getTokenFromUrl(url: string): string {
    const urlSearchParams = new URL(`xx://dumm.y/${url}`).searchParams;
    const token = urlSearchParams.get('token');
    Iif (!token) {
      throw new Error('No token!');
    }
    return token;
  }
 
  handleDisconnect(client: WebSocket): void {
    let disconnectedToken = '';
    Object.keys(this.clients).forEach((token: string) => {
      if (this.clients[token] === client) {
        delete this.clients[token];
        disconnectedToken = token;
      }
    });
 
    if (disconnectedToken !== '') {
      this.clientLost$.next(disconnectedToken);
      this.clientsCount$.next(Object.values(this.clients).length);
      this.logger.log(`client disconnected: ${disconnectedToken}`);
    }
  }
 
  broadcastToRegistered(tokens: string[], event: BroadcastingEvent, message: any): void {
    const payload = JSON.stringify({ event, data: message });
 
    tokens.forEach((token: string) => {
      if (typeof this.clients[token] !== 'undefined') {
        this.logger.log(`sending to client: ${token}`);
        this.clients[token].send(payload);
      }
    });
  }
 
  disconnectClient(monitorToken: string): void {
    if (typeof this.clients[monitorToken] !== 'undefined') {
      this.logger.log(`disconnect client: ${monitorToken}`);
      this.clients[monitorToken].close();
      delete this.clients[monitorToken];
    }
  }
 
  disconnectAll(): void {
    Object.keys(this.clients).forEach((token: string) => {
      this.disconnectClient(token);
    });
  }
 
  getDisconnectionObservable(): Observable<string> {
    return this.clientLost$.asObservable();
  }
 
  getClientTokens(): string[] {
    return Object.keys(this.clients);
  }
 
  @SubscribeMessage('subscribe:client.count')
  subscribeClientCount(@MessageBody() data: number): Observable<WsResponse<number>> {
    return this.clientsCount$.pipe(map((count: number) => ({ event: 'client.count', data: count })));
  }
}