socket/Transaction.js

/**
 * @module flitter-socket/Transaction
 */

const uuid = require('uuid/v4')

/**
 * Object representing one exchange between two parties in
 * a websocket connection. Handles response sending and request
 * data.
 * @class
 */
class Transaction {
    
    constructor(data, connection_manager){
        /**
         * Whether the transaction has completed. That is, has the
         * recipient sent a response.
         * @type {boolean}
         */
        this.resolved = false

        /**
         * Universally-unique id of this transaction. Used by both
         * parties to match up requests and responses.
         * @type {string}
         */
        this.id = data.transaction_id ? data.transaction_id : uuid()

        /**
         * The open websocket used for communication
         * @type {Socket}
         */
        this.socket = connection_manager.socket

        /**
         * Contains the outgoing data for the transaction.
         * That is, the data sent/to be sent back to the sender.
         * @type {Object}
         */
        this.outgoing = data.outgoing ? data.outgoing : {}

        /**
         * Contains the incoming data for the transaction.
         * That is, the data from the sender.
         * @type {Object}
         */
        this.incoming = data.incoming ? data.incoming : {}

        /**
         * Unique ID of the websocket client's connection in the connection manager.
         * @type {string}
         */
        this.connection_id = connection_manager.id

        /**
         * Is this a request or a response-type transaction?
         * @type {"request"|"response"}
         */
        this.type = data.type ? data.type : 'request'

        /**
         * Set to true if 1. this transaction is a response and 2. the response has been sent
         * @type {boolean}
         */
        this.sent = false

        /**
         * Set to true if 1. the transaction was awaiting data and 2. the data has been received
         * @type {boolean}
         */
        this.received = false

        /**
         * HTTP-equivalent status code for this transaction. Use this.status() to get/set.
         * @type {number}
         * @private
         */
        this._status = 200

        /**
         * Message to be included in the response. User this.message() to get/set.
         * @type {string}
         * @private
         */
        this._message = ""

        /**
         * The connection manager that spawned this transaction.
         * @type {module:flitter-socket/ConnectionManager~ConnectionManager}
         */
        this.cm = connection_manager
    }

    /**
     * Mark the transaction as resolved.
     */
    resolve(){
        this.resolved = true
    }

    /**
     * Get or set the status code. If a new code is provided, set the status code and return
     * this for chaining. Otherwise, get the current status code.
     * @param {number} [code]
     * @returns {Transaction|number} - if code is provided, set the code and return this; otherwise return the current code
     */
    status(code = null){
        if ( code ){
            this._status = code
            return this
        }

        return this._status
    }

    /**
     * Get or set the message. If a new message is provided, set the message and return
     * this for chaining. Otherwise get the current message.
     * @param {string} [msg]
     * @returns {Transaction|string} - if message is provided, set the message and return this; otherwise return the current message
     */
    message(msg = null){
        if ( msg ){
            this._message = msg
            return this
        }

        return this._message
    }

    /**
     * Send data to the recipient of this transaction as stringified JSON, and resolve the transaction.
     * @param data
     * @returns {*|boolean|void}
     */
    send(data){
        if ( this.type === 'request' ){
            if ( this.resolved ) throw new Error(`Transaction can only be sent once per request. (ID: ${this.id})`)

            const obj = {
                status: this._status,
                transaction_id: this.id,
                type: 'response',
                ... (this._message && {message: this._message}),
                ... (data && {data}),
                ... ((!data && this.outgoing) && {data: this.outgoing})
            }

            this.json = JSON.stringify(obj)
            this.resolve()
        }
        else if ( this.type === 'response' ){
            if ( this.sent ) throw new Error(`Request can only be sent once per Transaction. (ID: ${this.id})`)

            const obj = {
                endpoint: this.endpoint,
                transaction_id: this.id,
                type: 'request',
                ... (data && {data}),
                ... ((!data && this.outgoing) && {data: this.outgoing})
            }

            this.json = JSON.stringify(obj)
        }
        
        this.sent = true
        return this.socket.send(this.json)
    }
    
}

module.exports = exports = Transaction