UNPKG

@akanass/rx-socket-client

Version:

Reconnectable websocket client with RxJS Subject

337 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RxSocketClientSubject = void 0; const buffer_1 = require("buffer"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const webSocket_1 = require("rxjs/webSocket"); const ws = require("websocket"); /** * Class definition */ class RxSocketClientSubject extends rxjs_1.Subject { /** * Class constructor * * @param urlConfigOrSource */ constructor(urlConfigOrSource) { super(); // define connection status subject this._connectionStatus$ = new rxjs_1.Subject(); // set reconnect interval if (urlConfigOrSource.reconnectInterval) { this._reconnectInterval = urlConfigOrSource.reconnectInterval; } else { this._reconnectInterval = 5000; } // set reconnect attempts if (urlConfigOrSource.reconnectAttempts) { this._reconnectAttempts = urlConfigOrSource.reconnectAttempts; } else { this._reconnectAttempts = 10; } // check type of constructor's parameter to add url in config if (typeof urlConfigOrSource === 'string') { // create minimum config object this._wsSubjectConfig = Object.assign({}, { url: urlConfigOrSource }); } else { // create minimum config object this._wsSubjectConfig = Object.assign({}, { url: urlConfigOrSource.url }); } // add protocol in config if (urlConfigOrSource.protocol) { Object.assign(this._wsSubjectConfig, { protocol: urlConfigOrSource.protocol }); } // node environment if (typeof window === 'undefined') { Object.assign(this._wsSubjectConfig, { WebSocketCtor: ws['w3cwebsocket'] }); } // add WebSocketCtor in config if (urlConfigOrSource.WebSocketCtor) { Object.assign(this._wsSubjectConfig, { WebSocketCtor: urlConfigOrSource.WebSocketCtor }); } // add binaryType in config if (urlConfigOrSource.binaryType) { Object.assign(this._wsSubjectConfig, { binaryType: urlConfigOrSource.binaryType }); } // add default data in config Object.assign(this._wsSubjectConfig, { deserializer: this._deserializer, serializer: this._serializer, openObserver: { next: () => { this._connectionStatus$.next(true); } }, closeObserver: { next: () => { this._cleanSocket(); this._connectionStatus$.next(false); } } }); // connect socket this._connect(); // connection status subscription this.connectionStatus$.subscribe({ next: isConnected => { if (!this._reconnectionObservable && typeof (isConnected) === 'boolean' && !isConnected) { this._reconnect(); } } }); } /** * Returns connection status observable * * @return {Observable<boolean>} */ get connectionStatus$() { return this._connectionStatus$ .pipe((0, operators_1.distinctUntilChanged)()); } /** * Function to send data by socket * * @param data */ send(data) { this._socket.next(data); } /** * Function to handle text response for given event from server * * @example <caption>UTF Text Message from server</caption> * * const message = { * type: 'utf8', * utf8Data: { * event: 'data', * data: 'Data from the server' * } * } * * @example <caption>Simple Text Message from server</caption> * * const message = { * event: 'data', * data: 'Data from the server' * } * * @param event represents value inside {utf8Data.event} or {event} from server response * * @value complete | <any> * @example <caption>Event type</caption> * * if (event === 'complete') => handle Observable's complete * else handle Observable's success * * @param cb is the function executed if event matches the response from the server */ on(event, cb) { this._message$(event) .subscribe({ next: (message) => cb(message.data), /* istanbul ignore next */ error: () => undefined, complete: () => { /* istanbul ignore else */ if (event === 'close') { cb(); } } }); } /** * Function to handle bytes response from server * * @example <caption>Bytes Message from server</caption> * * const message = { * type: 'binary', * binaryData: <Buffer 74 6f 74 6f> * } * * @example <caption>Simple Bytes Message from server</caption> * * const message = <Buffer 74 6f 74 6f> * * @param cb is the function executed if event matches the response from the server */ onBytes(cb) { this.onBytes$() .subscribe({ next: (message) => cb(message) }); } /** * Same as `on` method but with Observable response * * @param event represents value inside {utf8Data.event} or {event} from server response * * @return {Observable<any>} */ on$(event) { return this._message$(event) .pipe((0, operators_1.map)(_ => _.data)); } /** * Function to handle socket close event from server with Observable * * @return {Observable<void>} */ onClose$() { return new rxjs_1.Observable(observer => { this.subscribe({ /* istanbul ignore next */ next: () => undefined, /* istanbul ignore next */ error: () => undefined, complete: () => { observer.next(); observer.complete(); } }); }); } /** * Returns formatted binary from server with Observable * * @return {Observable<WebSocketBinaryServer>} * * @private */ onBytes$() { return this .pipe((0, operators_1.map)((message) => (message.type && message.type === 'binary' && message.binaryData) ? message.binaryData : message)); } /** * Function to emit data for given event to server * * @param event type of data for the server request * @param data request data */ emit(event, data) { this.send({ event, data }); } /** * Returns formatted and filtered message from server for given event with Observable * * @param {string | 'close'} event represents value inside {utf8Data.event} or {event} from server response * * @return {Observable<WebSocketMessageServer>} * * @private */ _message$(event) { return this .pipe((0, operators_1.map)((message) => (message.type && message.type === 'utf8' && message.utf8Data) ? message.utf8Data : message), (0, operators_1.filter)((message) => message.event && message.event !== 'close' && message.event === event && message.data)); } /** * Function to clean socket data * * @private */ _cleanSocket() { /* istanbul ignore else */ if (this._socketSubscription) { this._socketSubscription.unsubscribe(); } this._socket = undefined; } /** * Function to clean reconnection data * * @private */ _cleanReconnection() { /* istanbul ignore else */ if (this._reconnectionSubscription) { this._reconnectionSubscription.unsubscribe(); } this._reconnectionObservable = undefined; } /** * Function to create socket and subscribe to it * * @private */ _connect() { this._socket = new webSocket_1.WebSocketSubject(this._wsSubjectConfig); this._socketSubscription = this._socket.subscribe({ next: (m) => { this.next(m); }, error: () => { /* istanbul ignore if */ if (!this._socket) { this._cleanReconnection(); this._reconnect(); } } }); } /** * Function to reconnect socket * * @private */ _reconnect() { this._reconnectionObservable = (0, rxjs_1.interval)(this._reconnectInterval) .pipe((0, operators_1.takeWhile)((v, index) => index < this._reconnectAttempts && !this._socket)); this._reconnectionSubscription = this._reconnectionObservable.subscribe({ next: () => this._connect(), /* istanbul ignore next */ error: () => undefined, complete: () => { this._cleanReconnection(); if (!this._socket) { this.complete(); this._connectionStatus$.complete(); } } }); } /** * Default deserializer * * @param e * * @return {any} * @private */ _deserializer(e) { try { return JSON.parse(e.data); } catch (err) { return e.data; } } ; /** * Default serializer * * @param data * * @return {WebSocketMessage} * @private */ _serializer(data) { return typeof (data) === 'string' || buffer_1.Buffer.isBuffer(data) ? data : JSON.stringify(data); } ; } exports.RxSocketClientSubject = RxSocketClientSubject; //# sourceMappingURL=rx-socket-client.subject.js.map