@akanass/rx-socket-client
Version:
Reconnectable websocket client with RxJS Subject
337 lines • 10.1 kB
JavaScript
"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