All files / microservices/client client-mqtt.ts

90.74% Statements 49/54
81.25% Branches 13/16
87.5% Functions 14/16
90.74% Lines 49/54
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 1401x 1x 1x 1x 1x                 1x 1x   1x   1x 2x         2x 2x 2x     2x     6x       4x       2x 2x 2x       3x     3x 3x   3x 3x               3x             1x   1x     1x               4x   1x         3x 3x       3x 3x     3x 1x           2x                     6x 6x 5x 5x   5x 5x     5x 5x         5x 2x 2x     1x        
import { Logger } from '@nestjs/common/services/logger.service';
import { loadPackage } from '@nestjs/common/utils/load-package.util';
import { fromEvent, merge, Observable } from 'rxjs';
import { first, map, share, tap } from 'rxjs/operators';
import {
  CLOSE_EVENT,
  ERROR_EVENT,
  MESSAGE_EVENT,
  MQTT_DEFAULT_URL,
} from '../constants';
import { MqttClient } from '../external/mqtt-client.interface';
import { MqttOptions, PacketId, ReadPacket, WritePacket } from '../interfaces';
import { ClientOptions } from '../interfaces/client-metadata.interface';
import { ClientProxy } from './client-proxy';
import { ECONNREFUSED } from './constants';
 
let mqttPackage: any = {};
 
export class ClientMqtt extends ClientProxy {
  protected readonly logger = new Logger(ClientProxy.name);
  protected readonly url: string;
  protected mqttClient: MqttClient;
  protected connection: Promise<any>;
 
  constructor(protected readonly options: ClientOptions['options']) {
    super();
    this.url =
      this.getOptionsProp<MqttOptions>(this.options, 'url') || MQTT_DEFAULT_URL;
 
    mqttPackage = loadPackage('mqtt', ClientMqtt.name);
  }
  public getAckPatternName(pattern: string): string {
    return `${pattern}_ack`;
  }
 
  public getResPatternName(pattern: string): string {
    return `${pattern}_res`;
  }
 
  public close() {
    this.mqttClient && this.mqttClient.end();
    this.mqttClient = null;
    this.connection = null;
  }
 
  public connect(): Promise<any> {
    Iif (this.mqttClient) {
      return this.connection;
    }
    this.mqttClient = this.createClient();
    this.handleError(this.mqttClient);
 
    const connect$ = this.connect$(this.mqttClient);
    this.connection = this.mergeCloseEvent(this.mqttClient, connect$)
      .pipe(
        tap(() =>
          this.mqttClient.on(MESSAGE_EVENT, this.createResponseCallback()),
        ),
        share(),
      )
      .toPromise();
    return this.connection;
  }
 
  public mergeCloseEvent<T = any>(
    instance: MqttClient,
    source$: Observable<T>,
  ): Observable<T> {
    const close$ = fromEvent(instance, CLOSE_EVENT).pipe(
      map(err => {
        throw err;
      }),
    );
    return merge(source$, close$).pipe(first());
  }
 
  public createClient(): MqttClient {
    return mqttPackage.connect(this.url, this.options as MqttOptions);
  }
 
  public handleError(client: MqttClient) {
    client.addListener(
      ERROR_EVENT,
      err => err.code !== ECONNREFUSED && this.logger.error(err),
    );
  }
 
  public createResponseCallback(): (channel: string, buffer) => any {
    return (channel: string, buffer: Buffer) => {
      const { err, response, isDisposed, id } = JSON.parse(
        buffer.toString(),
      ) as WritePacket & PacketId;
 
      const callback = this.routingMap.get(id);
      Iif (!callback) {
        return undefined;
      }
      if (isDisposed || err) {
        return callback({
          err,
          response: null,
          isDisposed: true,
        });
      }
      callback({
        err,
        response,
      });
    };
  }
 
  protected publish(
    partialPacket: ReadPacket,
    callback: (packet: WritePacket) => any,
  ): Function {
    try {
      const packet = this.assignPacketId(partialPacket);
      const pattern = this.normalizePattern(partialPacket.pattern);
      const responseChannel = this.getResPatternName(pattern);
 
      this.mqttClient.subscribe(responseChannel, err => {
        Iif (err) {
          return;
        }
        this.routingMap.set(packet.id, callback);
        this.mqttClient.publish(
          this.getAckPatternName(pattern),
          JSON.stringify(packet),
        );
      });
      return () => {
        this.mqttClient.unsubscribe(responseChannel);
        this.routingMap.delete(packet.id);
      };
    } catch (err) {
      callback({ err });
    }
  }
}