UNPKG

webstomp-obs

Version:

Stomp over websocket using observable

126 lines (107 loc) 4.68 kB
import { ConnectedHeaders } from './headers'; import {BYTES, sizeOfUTF8, trim} from './utils'; export interface FrameHeader { [key: string]: any; subscription: string; ack?: any; 'message-id'?: string; transaction?: string; version?: string; } // [STOMP Frame](http://stomp.github.com/stomp-specification-1.1.html#STOMP_Frames) Class class Frame { public command: string; public headers: FrameHeader; public body: any; public ack: any; public nack: any; // Frame constructor constructor(command: string, headers: FrameHeader = {} as FrameHeader, body: any = '') { this.command = command; this.headers = headers; this.body = body; } // Provides a textual representation of the frame // suitable to be sent to the server public toString = () => { let lines: string[] = [this.command], skipContentLength: boolean = this.headers['content-length'] === false ; if (skipContentLength) delete this.headers['content-length']; Object.keys(this.headers).forEach(name => { let value: any = this.headers[name]; lines.push(`${name}:${value}`); }); if (this.body && !skipContentLength) { lines.push(`content-length:${sizeOfUTF8(this.body)}`); } lines.push(BYTES.LF + this.body); return lines.join(BYTES.LF); } // Unmarshall a single STOMP frame from a `data` string public static unmarshallSingle(data: string) { // search for 2 consecutives LF byte to split the command // and headers from the body let divider: number = data.search(new RegExp(BYTES.LF + BYTES.LF)), headerLines: string[] = data.substring(0, divider).split(BYTES.LF), command: string = headerLines.shift(), headers: FrameHeader = {} as FrameHeader, body = '', // skip the 2 LF bytes that divides the headers from the body bodyIndex = divider + 2; // Parse headers in reverse order so that for repeated headers, the 1st // value is used for (let line of headerLines.reverse()) { let idx: number = line.indexOf(':'); headers[trim(line.substring(0, idx))] = trim(line.substring(idx + 1)); } // Parse body // check for content-length or topping at the first NULL byte found. if (headers['content-length']) { let len: number = parseInt(headers['content-length'], 10); body = ('' + data).substring(bodyIndex, bodyIndex + len); } else { let chr: string = null; for (let i: number = bodyIndex; i < data.length; i++) { chr = data.charAt(i); if (chr === BYTES.NULL) break; body += chr; } } return new Frame(command, headers, body); } // Split the data before unmarshalling every single STOMP frame. // Web socket servers can send multiple frames in a single websocket message. // If the message size exceeds the websocket message size, then a single // frame can be fragmented across multiple messages. // // `datas` is a string. // // returns an *array* of Frame objects public static unmarshall(datas: string) { // split and unmarshall *multiple STOMP frames* contained in a *single WebSocket frame*. // The data is split when a NULL byte (followed by zero or many LF bytes) is found let frames = datas.split(new RegExp(BYTES.NULL + BYTES.LF + '*')), firstFrames = frames.slice(0, -1), lastFrame = frames.slice(-1)[0], r = { frames: firstFrames.map(f => Frame.unmarshallSingle(f)), partial: '' }; // If this contains a final full message or just a acknowledgement of a PING // without any other content, process this frame, otherwise return the // contents of the buffer to the caller. if (lastFrame === BYTES.LF || (lastFrame.search(RegExp(BYTES.NULL + BYTES.LF + '*$'))) !== -1) { r.frames.push(Frame.unmarshallSingle(lastFrame)); } else { r.partial = lastFrame; } return r; } // Marshall a Stomp frame public static marshall(command: string, headers?: FrameHeader, body?: string): any { let frame = new Frame(command, headers, body); return frame.toString() + BYTES.NULL; } } export default Frame;