UNPKG

webstomp-obs

Version:

Stomp over websocket using observable

154 lines (127 loc) 7.99 kB
import { Observable } from 'rxjs/Observable'; import { Observer } from 'rxjs/Observer'; import { Subject } from 'rxjs/Subject'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/filter'; import { IEvent, IProtocol, IConnectedObservable, IWebSocketObservable, IWebSocketHandler, WsOptions, IWebSocket } from '../../types'; import Frame from '../../frame'; import { ACK, ConnectionHeaders, SubscribeHeaders, UnsubscribeHeaders } from '../../headers'; import { typedArrayToUnicodeString, logger, parseData } from '../../utils'; import WebSocketHandler from '../../webSocketHandler'; import stompProtocol from './stompProtocol'; import Heartbeat from '../../heartbeat'; const parseHeartbeatSettings = (heartbeat: Heartbeat): string => [heartbeat.heartbeatSettings.outgoing, heartbeat.heartbeatSettings.incoming].join(',') // STOMP Handler Class // // Using stompProtocol // const stompWebSocketHandler = (createWsConnection: () => IWebSocket, options: WsOptions): IWebSocketHandler<IConnectedObservable> => { const wsHandler: WebSocketHandler = new WebSocketHandler(createWsConnection, options); // creating the heartbeat const heartbeat = new Heartbeat(options.heartbeat); const initConnection = (headers: ConnectionHeaders): Observable<IConnectedObservable> => { let disconnectFn; let currentHeaders: ConnectionHeaders = {...headers} // Check if we already have heart-beat in headers before adding them if (!headers['heart-beat']) { currentHeaders['heart-beat'] = parseHeartbeatSettings(heartbeat); } return wsHandler.initConnection(currentHeaders).switchMap((wsConnection: IWebSocketObservable) => { let counter: number = 0; let partialData: string = ''; let version: string = ''; let currentProtocol: IProtocol = stompProtocol(); // we initialise the current protocol with no version as we need it for CONNECT const _parseMessageReceived = (evt: IEvent): Frame[] => { const unmarshalledData = parseData(evt.data, partialData, currentProtocol.hearbeatMsg && currentProtocol.hearbeatMsg()); if(!unmarshalledData) { return; } partialData = unmarshalledData.partial; return unmarshalledData.frames; } const unSubscribe = (_headers: UnsubscribeHeaders) => wsConnection.messageSender.next(currentProtocol.unSubscribe(_headers)) return <Observable<IConnectedObservable>>Observable.create((connectionObserver: Observer<IConnectedObservable>) => { const stompMessageReceived = new Subject() const stompMessageReceipted = new Subject() const errorReceived = new Subject() const subscribeTo = (destination: string, _headers: {id?: string, ack?: ACK} = {}): Observable<Frame> => { const id = _headers.id || 'sub-' + counter++; const currentHeader: SubscribeHeaders = {destination, ack: _headers.ack, id }; wsConnection.messageSender.next(currentProtocol.subscribe(currentHeader)); return <Observable<Frame>>stompMessageReceived.finally(() => unSubscribe({id})).filter( (frame: Frame) => frame.headers.subscription === id ); } // subscribing to message received const msgSubscription = wsConnection.messageReceived.subscribe((evt: IEvent) => { heartbeat.activityFromServer(); const dataFrames = _parseMessageReceived(evt); dataFrames && dataFrames.forEach( (frame: Frame) => { switch (frame.command) { case 'CONNECTED': version = frame.headers.version; currentProtocol = stompProtocol(version); logger.debug(`connected to server ${frame.headers.server}`); // we start heartbeat only if the protocol support it const hearbeatMsg = currentProtocol.hearbeatMsg(); if (hearbeatMsg) { const [serverOutgoing, serverIncoming] = (frame.headers['heart-beat'] || '0,0').split(',').map((v: string) => parseInt(v, 10)); heartbeat.startHeartbeat(serverOutgoing, serverIncoming, { sendPing: () => wsConnection.messageSender.next(hearbeatMsg), close: (error) => connectionObserver.error(error) }); } connectionObserver.next({ subscribeTo: subscribeTo, messageReceipted: <Observable<Frame>>stompMessageReceipted.asObservable(), errorReceived: <Observable<Frame>>errorReceived.asObservable(), messageSender: wsConnection.messageSender, protocol: currentProtocol } ); break; case 'MESSAGE': const subscription: string = currentProtocol.getSubscription(frame) const messageID: string = currentProtocol.getMessageId(frame); frame.ack = () => wsConnection.messageSender.next(currentProtocol.ack(messageID, subscription)); currentProtocol.nack && (frame.nack = () => wsConnection.messageSender.next(currentProtocol.nack(messageID, subscription))); stompMessageReceived.next(frame) break; case 'RECEIPT': stompMessageReceipted.next(frame); break; case 'ERROR': errorReceived.next(frame); break; default: logger.debug(`Unhandled frame: ${frame}`); } }); }) // sending connect wsConnection.messageSender.next(currentProtocol.connect(currentHeaders)); disconnectFn = () => { heartbeat.stopHeartbeat(); wsConnection.messageSender.next(currentProtocol.disconnect({receipt: `${counter++}`})); } return () => { msgSubscription && msgSubscription.unsubscribe(); stompMessageReceived.complete(); stompMessageReceipted.complete(); errorReceived.complete(); } }) }).finally(() => { disconnectFn && disconnectFn(); }) } return { initConnection }; } export default stompWebSocketHandler