UNPKG

dynatrace-cordova-outsystems-plugin

Version:

This plugin gives you the ability to use the Dynatrace instrumentation in your hybrid application (Cordova, Ionic, ..). It uses the Mobile Agent, the JavaScript Agent and the Javascript Bridge. The Mobile Agent will give you all device specific values con

359 lines (319 loc) 9.48 kB
'use strict'; const { randomBytes } = require('crypto'); const PerMessageDeflate = require('./permessage-deflate'); const { EMPTY_BUFFER } = require('./constants'); const { isValidStatusCode } = require('./validation'); const { mask: applyMask, toBuffer } = require('./buffer-util'); /** * HyBi Sender implementation. */ class Sender { /** * Creates a Sender instance. * * @param {net.Socket} socket The connection socket * @param {Object} extensions An object containing the negotiated extensions */ constructor(socket, extensions) { this._extensions = extensions || {}; this._socket = socket; this._firstFragment = true; this._compress = false; this._bufferedBytes = 0; this._deflating = false; this._queue = []; } /** * Frames a piece of data according to the HyBi WebSocket protocol. * * @param {Buffer} data The data to frame * @param {Object} options Options object * @param {Number} options.opcode The opcode * @param {Boolean} options.readOnly Specifies whether `data` can be modified * @param {Boolean} options.fin Specifies whether or not to set the FIN bit * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @return {Buffer[]} The framed data as a list of `Buffer` instances * @public */ static frame(data, options) { const merge = options.mask && options.readOnly; var offset = options.mask ? 6 : 2; var payloadLength = data.length; if (data.length >= 65536) { offset += 8; payloadLength = 127; } else if (data.length > 125) { offset += 2; payloadLength = 126; } const target = Buffer.allocUnsafe(merge ? data.length + offset : offset); target[0] = options.fin ? options.opcode | 0x80 : options.opcode; if (options.rsv1) target[0] |= 0x40; target[1] = payloadLength; if (payloadLength === 126) { target.writeUInt16BE(data.length, 2); } else if (payloadLength === 127) { target.writeUInt32BE(0, 2); target.writeUInt32BE(data.length, 6); } if (!options.mask) return [target, data]; const mask = randomBytes(4); target[1] |= 0x80; target[offset - 4] = mask[0]; target[offset - 3] = mask[1]; target[offset - 2] = mask[2]; target[offset - 1] = mask[3]; if (merge) { applyMask(data, mask, target, offset, data.length); return [target]; } applyMask(data, mask, data, 0, data.length); return [target, data]; } /** * Sends a close message to the other peer. * * @param {(Number|undefined)} code The status code component of the body * @param {String} data The message component of the body * @param {Boolean} mask Specifies whether or not to mask the message * @param {Function} cb Callback * @public */ close(code, data, mask, cb) { var buf; if (code === undefined) { buf = EMPTY_BUFFER; } else if (typeof code !== 'number' || !isValidStatusCode(code)) { throw new TypeError('First argument must be a valid error code number'); } else if (data === undefined || data === '') { buf = Buffer.allocUnsafe(2); buf.writeUInt16BE(code, 0); } else { buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); buf.writeUInt16BE(code, 0); buf.write(data, 2); } if (this._deflating) { this.enqueue([this.doClose, buf, mask, cb]); } else { this.doClose(buf, mask, cb); } } /** * Frames and sends a close message. * * @param {Buffer} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Function} cb Callback * @private */ doClose(data, mask, cb) { this.sendFrame( Sender.frame(data, { fin: true, rsv1: false, opcode: 0x08, mask, readOnly: false }), cb ); } /** * Sends a ping message to the other peer. * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Function} cb Callback * @public */ ping(data, mask, cb) { const buf = toBuffer(data); if (this._deflating) { this.enqueue([this.doPing, buf, mask, toBuffer.readOnly, cb]); } else { this.doPing(buf, mask, toBuffer.readOnly, cb); } } /** * Frames and sends a ping message. * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} readOnly Specifies whether `data` can be modified * @param {Function} cb Callback * @private */ doPing(data, mask, readOnly, cb) { this.sendFrame( Sender.frame(data, { fin: true, rsv1: false, opcode: 0x09, mask, readOnly }), cb ); } /** * Sends a pong message to the other peer. * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Function} cb Callback * @public */ pong(data, mask, cb) { const buf = toBuffer(data); if (this._deflating) { this.enqueue([this.doPong, buf, mask, toBuffer.readOnly, cb]); } else { this.doPong(buf, mask, toBuffer.readOnly, cb); } } /** * Frames and sends a pong message. * * @param {*} data The message to send * @param {Boolean} mask Specifies whether or not to mask `data` * @param {Boolean} readOnly Specifies whether `data` can be modified * @param {Function} cb Callback * @private */ doPong(data, mask, readOnly, cb) { this.sendFrame( Sender.frame(data, { fin: true, rsv1: false, opcode: 0x0a, mask, readOnly }), cb ); } /** * Sends a data message to the other peer. * * @param {*} data The message to send * @param {Object} options Options object * @param {Boolean} options.compress Specifies whether or not to compress `data` * @param {Boolean} options.binary Specifies whether `data` is binary or text * @param {Boolean} options.fin Specifies whether the fragment is the last one * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Function} cb Callback * @public */ send(data, options, cb) { const buf = toBuffer(data); const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; var opcode = options.binary ? 2 : 1; var rsv1 = options.compress; if (this._firstFragment) { this._firstFragment = false; if (rsv1 && perMessageDeflate) { rsv1 = buf.length >= perMessageDeflate._threshold; } this._compress = rsv1; } else { rsv1 = false; opcode = 0; } if (options.fin) this._firstFragment = true; if (perMessageDeflate) { const opts = { fin: options.fin, rsv1, opcode, mask: options.mask, readOnly: toBuffer.readOnly }; if (this._deflating) { this.enqueue([this.dispatch, buf, this._compress, opts, cb]); } else { this.dispatch(buf, this._compress, opts, cb); } } else { this.sendFrame( Sender.frame(buf, { fin: options.fin, rsv1: false, opcode, mask: options.mask, readOnly: toBuffer.readOnly }), cb ); } } /** * Dispatches a data message. * * @param {Buffer} data The message to send * @param {Boolean} compress Specifies whether or not to compress `data` * @param {Object} options Options object * @param {Number} options.opcode The opcode * @param {Boolean} options.readOnly Specifies whether `data` can be modified * @param {Boolean} options.fin Specifies whether or not to set the FIN bit * @param {Boolean} options.mask Specifies whether or not to mask `data` * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit * @param {Function} cb Callback * @private */ dispatch(data, compress, options, cb) { if (!compress) { this.sendFrame(Sender.frame(data, options), cb); return; } const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; this._deflating = true; perMessageDeflate.compress(data, options.fin, (_, buf) => { this._deflating = false; options.readOnly = false; this.sendFrame(Sender.frame(buf, options), cb); this.dequeue(); }); } /** * Executes queued send operations. * * @private */ dequeue() { while (!this._deflating && this._queue.length) { const params = this._queue.shift(); this._bufferedBytes -= params[1].length; params[0].apply(this, params.slice(1)); } } /** * Enqueues a send operation. * * @param {Array} params Send operation parameters. * @private */ enqueue(params) { this._bufferedBytes += params[1].length; this._queue.push(params); } /** * Sends a frame. * * @param {Buffer[]} list The frame to send * @param {Function} cb Callback * @private */ sendFrame(list, cb) { if (list.length === 2) { this._socket.cork(); this._socket.write(list[0]); this._socket.write(list[1], cb); this._socket.uncork(); } else { this._socket.write(list[0], cb); } } } module.exports = Sender;