{"version":3,"file":"ispsystem-websocket.mjs","sources":["../../src/lib/model/isp-websocket-state.enum.ts","../../src/lib/isp-websocket.model.ts","../../src/ispsystem-websocket.ts"],"sourcesContent":["/**\n * Статусы обертки для сокета\n */\nexport enum ISPWebsocketState {\n  CONNECTED = WebSocket.OPEN,\n  DISCONNECTED = WebSocket.CLOSED,\n}\n","import {\r\n  ConnectableObservable,\r\n  fromEvent,\r\n  merge,\r\n  Observable,\r\n  of,\r\n  Subject,\r\n  Subscription,\r\n  throwError,\r\n  timer,\r\n} from 'rxjs';\r\nimport {\r\n  concatMap,\r\n  delayWhen,\r\n  filter,\r\n  map,\r\n  mergeMap,\r\n  publishReplay,\r\n  take,\r\n  tap,\r\n} from 'rxjs/operators';\r\n\r\nimport { ISPWebsocketEvent } from './model/isp-websocket-event.interface';\r\nimport { ISPWebsocketState } from './model/isp-websocket-state.enum';\r\n\r\nconst DEFAULT_RECONNECT_COUNT = 0;\r\nconst DEFAULT_RECONNECT_DELAY_MS = 5000;\r\n\r\nconst WEBSOCKET_NORMAL_CLOSE_CODE = 1000;\r\nconst WEBSOCKET_EVENT_OPEN = 'open';\r\nconst WEBSOCKET_EVENT_CLOSE = 'close';\r\nconst WEBSOCKET_EVENT_ERROR = 'error';\r\nconst WEBSOCKET_EVENT_MESSAGE = 'message';\r\n\r\n/**\r\n * Класс - обертка для WebSocket\r\n * в возможностью переподключения\r\n */\r\nexport class ISPWebsocket<T = any> {\r\n  /**\r\n   * WebSocket\r\n   */\r\n  private ws: WebSocket;\r\n  /**\r\n   * индекс попытки подключения после ошибки\r\n   */\r\n  private connectIndex = 0;\r\n  /**\r\n   * кешируем url между переподключениями\r\n   */\r\n  private lastUrl: string;\r\n  /**\r\n   * кешируем host между переподключениями\r\n   */\r\n  private lastHost: string;\r\n  /**\r\n   * Connect event emitter\r\n   */\r\n  private connectSubject = new Subject<Event>();\r\n  /**\r\n   * Connect event stream subscription\r\n   */\r\n  private connectSub: Subscription;\r\n  /**\r\n   * Connect event\r\n   */\r\n  connect$ = this.connectSubject.asObservable();\r\n  /**\r\n   * Disconnect event emitter\r\n   */\r\n  private disconnectSubject = new Subject<CloseEvent>();\r\n  /**\r\n   * Disconnect event stream subscription\r\n   */\r\n  private disconnectSub: Subscription;\r\n  /**\r\n   * Disconnect event\r\n   */\r\n  disconnect$ = this.disconnectSubject.asObservable();\r\n  /**\r\n   * Error event emitter\r\n   */\r\n  private errorSubject = new Subject<Event>();\r\n  /**\r\n   * Error event stream subscription\r\n   */\r\n  private errorSub: Subscription;\r\n  /**\r\n   * Error event\r\n   */\r\n  error$ = this.errorSubject.asObservable();\r\n  /**\r\n   * Message event emitter\r\n   */\r\n  private messageSubject = new Subject<ISPWebsocketEvent<T>>();\r\n  /**\r\n   * Message event stream subscription\r\n   */\r\n  private messageSub: Subscription;\r\n  /**\r\n   * Message event\r\n   */\r\n  message$ = this.messageSubject.asObservable();\r\n\r\n  /**\r\n   * Поток true и false в зависимости от текущего статуса сокета\r\n   */\r\n  state$: ConnectableObservable<boolean> = merge(\r\n    this.connect$.pipe(map(() => true)),\r\n    this.disconnect$.pipe(map(() => false))\r\n    // превращаем Observable в ConnectableObservable, что бы сразу же (в конструкторе) подключиться к потоку событий\r\n  ).pipe(publishReplay(1)) as ConnectableObservable<boolean>;\r\n\r\n  /**\r\n   * Возвращает текущее состояние сокета\r\n   */\r\n  state(): ISPWebsocketState {\r\n    if (!this.ws || !Object.values(ISPWebsocketState).includes(this.ws.readyState)) {\r\n      return ISPWebsocketState.DISCONNECTED;\r\n    }\r\n\r\n    return this.ws.readyState;\r\n  }\r\n\r\n  /**\r\n   * Сокет подключен\r\n   */\r\n  get isConnected(): boolean {\r\n    return this.isState(ISPWebsocketState.CONNECTED);\r\n  }\r\n\r\n  /**\r\n   * Сокет отключен\r\n   */\r\n  get isDisconnected(): boolean {\r\n    return this.isState(ISPWebsocketState.DISCONNECTED);\r\n  }\r\n\r\n  /**\r\n   *\r\n   * @param reconnectCount - количество попыток переподключения\r\n   * @param reconnectDelay - задержка между переподключениями\r\n   * @param msgCountForSuccessConnect - количество сообщений, что бы подключение считалось успешным\r\n   */\r\n  constructor(\r\n    private reconnectCount: number = DEFAULT_RECONNECT_COUNT,\r\n    private reconnectDelay: number = DEFAULT_RECONNECT_DELAY_MS,\r\n    private msgCountForSuccessConnect: number = 0\r\n  ) {\r\n    this.state$.connect();\r\n  }\r\n\r\n  /**\r\n   * Сравнивает состояние с текущим\r\n   * @param state состояние\r\n   */\r\n  private isState(state: ISPWebsocketState): boolean {\r\n    return state === this.state();\r\n  }\r\n\r\n  /**\r\n   * Проверяет возможность переподключаться и переподключается\r\n   */\r\n  private tryReconnect(e: CloseEvent): Observable<Event> {\r\n    if (this.reconnectCount > this.connectIndex) {\r\n      return timer(this.reconnectDelay).pipe(concatMap(() => this.reconnect()));\r\n    }\r\n\r\n    return throwError(new Error(`Unexpected disconnect: ${e.code || ''} ${e.reason || ''}`));\r\n  }\r\n\r\n  /**\r\n   * Переподключается\r\n   */\r\n  private reconnect(): Observable<Event> {\r\n    return this.connect(this.lastUrl, this.lastHost);\r\n  }\r\n\r\n  /**\r\n   * Выставляет хуки\r\n   * @param ws сокет\r\n   */\r\n  private setListeners(ws: WebSocket): void {\r\n    let msgCount = 0;\r\n\r\n    const connectSub = fromEvent(ws, WEBSOCKET_EVENT_OPEN)\r\n      .pipe(\r\n        tap(\r\n          () => !this.msgCountForSuccessConnect && (this.connectIndex = 0),\r\n          e => this.errorSubject.error(e)\r\n        )\r\n      )\r\n      .subscribe(this.connectSubject);\r\n\r\n    const disconnectSub = fromEvent(ws, WEBSOCKET_EVENT_CLOSE)\r\n      .pipe(\r\n        tap(this.disconnectSubject),\r\n        filter(e => e.code !== WEBSOCKET_NORMAL_CLOSE_CODE),\r\n        mergeMap(e => this.tryReconnect(e))\r\n      )\r\n      .subscribe({ error: e => this.errorSubject.error(e) });\r\n\r\n    const errorSub = fromEvent(ws, WEBSOCKET_EVENT_ERROR)\r\n      .pipe(\r\n        tap(() => this.disconnectSubject.next()),\r\n        mergeMap(() => throwError(new Error(`Socket error`)))\r\n      )\r\n      .subscribe(this.errorSubject);\r\n\r\n    const messageSub = fromEvent(ws, WEBSOCKET_EVENT_MESSAGE)\r\n      .pipe(\r\n        tap(\r\n          () => {\r\n            if (this.msgCountForSuccessConnect) {\r\n              msgCount++;\r\n\r\n              if (this.msgCountForSuccessConnect <= msgCount) {\r\n                this.connectIndex = 0;\r\n              }\r\n            }\r\n          },\r\n          e => this.errorSubject.error(e)\r\n        )\r\n      )\r\n      .subscribe(this.messageSubject);\r\n\r\n    this.removeListeners();\r\n\r\n    this.connectSub = connectSub;\r\n    this.disconnectSub = disconnectSub;\r\n    this.errorSub = errorSub;\r\n    this.messageSub = messageSub;\r\n  }\r\n\r\n  /**\r\n   * Удаляет все хуки\r\n   */\r\n  private removeListeners(): void {\r\n    [this.connectSub, this.disconnectSub, this.messageSub, this.errorSub]\r\n      .filter(s => !!s)\r\n      .forEach(s => s.unsubscribe());\r\n  }\r\n\r\n  /**\r\n   * Подключение\r\n   * @param path путь\r\n   * @param host домен\r\n   */\r\n  connect(path: string, host: string = window.location.host): Observable<Event> {\r\n    this.lastUrl = path;\r\n    this.lastHost = host;\r\n    let protocol: 'ws' | 'wss' = 'ws';\r\n\r\n    if (window.location.protocol === 'https:') {\r\n      protocol = 'wss';\r\n    }\r\n\r\n    // соединяем домен и путь и избавляемся от дублирующихся слешей\r\n    const url = `${host}/${path}`.replace(/\\/\\/+/g, '/');\r\n    // добавляем протокол\r\n    const fullUrl = `${protocol}://${url}`;\r\n\r\n    // отключаемся перед подключением\r\n    return this.disconnect().pipe(\r\n      tap(() => {\r\n        // поднимаем индекс подключения\r\n        this.connectIndex++;\r\n        // пробуем подключиться\r\n        this.ws = new WebSocket(fullUrl);\r\n        this.setListeners(this.ws);\r\n      }),\r\n      // возврящаем хук на подключение\r\n      concatMap(() => this.connect$.pipe(take(1)))\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Отправляет строку в сокет\r\n   * @param data строка\r\n   */\r\n  send(data: string): Observable<void> {\r\n    return of(null).pipe(\r\n      // ждем активного состояния сокета (когда подключится / переподключится)\r\n      delayWhen(() => this.state$.pipe(filter(Boolean))),\r\n      tap(() => this.ws.send(data)),\r\n      map(() => undefined)\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Отключение\r\n   */\r\n  disconnect(): Observable<void> {\r\n    // если нет сокета возвращаем false\r\n    let obs$ = of(false);\r\n\r\n    // если есть сокет, смотрим на его состояние\r\n    if (this.ws) {\r\n      obs$ = this.state$.pipe(take(1));\r\n    }\r\n\r\n    return obs$.pipe(\r\n      concatMap(v => {\r\n        // если сокет неактивный возвращаем null\r\n        if (!v) {\r\n          return of(null);\r\n        }\r\n\r\n        // закрываем сокет\r\n        this.ws.close();\r\n\r\n        // возвращаем хук на отключение\r\n        return this.disconnect$.pipe(take(1));\r\n      })\r\n    );\r\n  }\r\n\r\n  /**\r\n   * Уничтожение обертки\r\n   */\r\n  destroy(): Observable<void> {\r\n    return this.disconnect().pipe(\r\n      tap(() => {\r\n        setTimeout(() => {\r\n          this.connectSubject.complete();\r\n          this.disconnectSubject.complete();\r\n          this.messageSubject.complete();\r\n          this.errorSubject.complete();\r\n        });\r\n      })\r\n    );\r\n  }\r\n}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}