webstomp-obs
Version:
Stomp over websocket using observable
129 lines (102 loc) • 4.41 kB
text/typescript
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import { Subject } from 'rxjs/Subject';
import { AnonymousSubscription } from 'rxjs/Subscription'
import { IEvent, IWebSocketObservable, WsOptions, IWebSocket } from './types';
import Frame from './frame';
import { ConnectionHeaders, DisconnectHeaders } from './headers';
import { unicodeStringToTypedArray, logger } from './utils';
export interface Subscription {
id: string;
unsubscribe: () => void;
}
// STOMP Client Class
//
// All STOMP protocol is exposed as methods of this class ('connect()',
// 'send()', etc.)
class WebSocketHandler {
private ws: IWebSocket
private isBinary: boolean
private connected: boolean
private maxWebSocketFrameSize: number
private createWS: Function
constructor(createWsConnection: () => IWebSocket, options: WsOptions) {
// cannot have default options object + destructuring in the same time in method signature
let {binary = false} = options;
this.createWS = createWsConnection;
this.isBinary = !!binary;
// maximum *IWebSocket* frame size sent by the client. If the STOMP frame
// is bigger than this value, the STOMP frame will be sent using multiple
// IWebSocket frames (default is 16KiB)
this.maxWebSocketFrameSize = 16 * 1024;
}
public initConnection = (headers: ConnectionHeaders): Observable<IWebSocketObservable> => {
return Observable.create((webSocketObserver: Observer<IWebSocketObservable>) => {
if (this.ws) {
throw 'Error, the connection has already been created !'
}
this.ws = this.createWS();
if (!this.ws) {
throw 'Error, createWsConnection function returned null !'
}
this.ws.binaryType = 'arraybuffer';
// Creating the Observables
const messageReceived = new Subject()
const messageReceipted = new Subject()
const messageSender = new Subject()
let inputSubscription: AnonymousSubscription
logger.debug('Opening Web Socket...');
this.ws.onopen = () => {
logger.debug('Web Socket Opened...');
inputSubscription = messageSender.subscribe(this._send)
webSocketObserver.next({messageReceived: <Observable<IEvent>>messageReceived.asObservable(), messageSender});
};
this.ws.onmessage = (evt: IEvent) => {
messageReceived.next(evt);
};
this.ws.onclose = (ev: CloseEvent) => {
logger.debug(`Whoops! Lost connection to ${this.ws.url}:`, ev);
this.ws = null;
webSocketObserver.error(ev);
};
return () => {
messageReceived.complete()
messageReceipted.complete()
inputSubscription && inputSubscription.unsubscribe()
this.disconnect()
}
})
}
public disconnect = (headers: DisconnectHeaders = {}) => {
if (this.ws) {
this.ws.onclose = null;
this.ws.close();
this.ws = null;
}
}
// Base method to transmit any stomp frame
private _send = (data: any) => {
if (!this.ws) {
logger.debug('Error, this.ws is null ! Possibly initConnection has not been called or not subscribed !');
return;
}
logger.debug(`>>> ${data}`);
this._wsSend(this.ws, data);
}
private _wsSend = (ws: IWebSocket, data: any) => {
if (this.isBinary) data = unicodeStringToTypedArray(data);
logger.debug(`>>> length ${data.length}`);
// if necessary, split the *STOMP* frame to send it on many smaller
// *IWebSocket* frames
while (true) {
if (data.length > this.maxWebSocketFrameSize) {
ws.send(data.slice(0, this.maxWebSocketFrameSize));
data = data.slice(this.maxWebSocketFrameSize);
logger.debug(`remaining = ${data.length}`);
} else {
return ws.send(data);
}
}
}
}
export default WebSocketHandler;