matrix-util.js 6.49 KB
'use strict';

var moment = require('moment');

var config;

var momentFormat = 'YYYY-MM-DD HH:mm:ss';
var defaultMaxHealthyWaitMs = 2 * 60 * 1000;

module.exports = MatrixUtil;

function _cleanPartnerId(partnerId) {
    let cleaned = partnerId;

    try {
        cleaned = cleaned.toLocaleString();
        cleaned = cleaned.trim().toLowerCase();
    } catch(e) {
        return partnerId;
    }

    return cleaned;
}

function getMaxHealthyWaitMs() {
    if (!config || !config.globals || !Number(config.globals.max_healthy_wait_ms)) { return defaultMaxHealthyWaitMs; }
    return Number(config.globals.max_healthy_wait_ms);
}

function MatrixUtil(options) {
    if (!options) {
        console.trace('Undefined options');
        process.exit(1);
    }

    this.matrix = options.matrix;
    if (!this.matrix) {
        console.trace("Matrix not set");
        process.exit(1);
    }

    this.logger = options.logger;
    if (!this.logger) {
        console.trace("Logger not set");
        process.exit(1);
    }

    config = options.config;
    if (!config) {
        console.trace("Config not set");
        process.exit(1);
    };
}

MatrixUtil.prototype.updateBuddyState = function(jid, state, statusText, resource) {
    if (!jid) {return; }
    if (jid == 'undefined') {return; }

    jid = _cleanPartnerId(jid);

    if (!resource) {
        resource = 'undefined';
    }

    let logger = this.logger;
    let matrix = this.matrix;

    logger.verbose('Buddy state change', {jid: jid, state: state, statusText: statusText, resource: resource});

    if (!matrix) {
        return;
    }

    if (!matrix.buddies) {
        matrix.buddies = {};
    }

    if (!matrix.buddies[jid]) {
        matrix.buddies[jid] = {resources: {}};
    }

    try {
        matrix.buddies[jid]['resources'][resource] = {
            state: state,
            statusText: statusText,
            last_update: moment().format(momentFormat)
        }
    }
    catch(e) {
        logger.warn('MatrixUtil: Exception on update resources on matrix', {jid: jid, state: state, statusText: statusText, resource: resource});
    }

    if (resource != 'undefined') {
        try {
            delete matrix.buddies[jid].resources['undefined'];
        }
        catch(e) {};
        }
}

MatrixUtil.prototype.isPartnerOffline = function(partner) {
    if (!partner) { return; }

    partner = _cleanPartnerId(partner);

    let matrix = this.matrix;
    let logger = this.logger;

    if (!matrix) { return false; }

    if (!matrix.buddies) { matrix.buddies = {}; }
    if (!matrix.buddies[partner]) { return true; }
    if (!matrix.buddies[partner].resources) { return true; };

    let resources = matrix.buddies[partner].resources;
    for (let key in resources) {
        if (resources.hasOwnProperty(key)) {
            let resource = resources[key];
            if (resources[key].state == 'online') {
                return false;
            }
        }
    }
    logger.verbose('Offline partner detected: ' + partner);
    return true;
}

MatrixUtil.prototype._isPartnerHealthy = function(partner) {
    if (!partner) { return; }
    partner = _cleanPartnerId(partner);

    if (this.isPartnerOffline(partner)) {
        return;
    }

    let matrix = this.matrix;
    let logger = this.logger;

    if (!matrix) { return false; }

    if (!matrix.buddies[partner]) { return false; }
    if (!matrix.buddies[partner]['waiting_for_response']) { return true; }
    if (!matrix.buddies[partner]['last_incoming']) { return false; }
    if (!matrix.buddies[partner]['last_incoming']['last_update_ts']) { return false; }

    let delta = Date.now() - Number(matrix.buddies[partner]['last_incoming']['last_update_ts']);
    let isHealthy = delta <= getMaxHealthyWaitMs();

    logger.verbose('Partner healthy analized', {partner: partner, isHealthy: isHealthy, delta: delta, maxHealthyWaitMs: getMaxHealthyWaitMs()});
    return isHealthy;
}

MatrixUtil.prototype.isPartnerHealthy = function(partner) {
    let matrix = this.matrix;

    let isHealthy = this._isPartnerHealthy(partner);

    if (!matrix.healthy_partners) {
        matrix.healthy_partners = [];
    }

    // update matrix
    let idx = matrix.healthy_partners.indexOf(partner);

    if (isHealthy) {
        if (idx < 0) {
            matrix.healthy_partners.push(partner);
        }
    }
    else {
        if (idx > -1) {
            matrix.healthy_partners.splice(idx, 1);
        }
    }

    return isHealthy;
}

MatrixUtil.prototype._updateLastResponseTime = function(partner) {
    let matrix = this.matrix;
    let logger = this.logger;

    if (!matrix.buddies[partner]['last_outgoing']) {
        logger.verbose('No outgoing yet, skip updateLastResponseTime');
        return;
    }

    if (!matrix.buddies[partner]['last_outgoing']['last_update_ts']) {
        logger.verbose('No outgoing timestamp yet, skip updateLastResponseTime');
        return;
    }

    if (
        matrix.buddies[partner]['last_incoming']
        && (Number(matrix.buddies[partner]['last_incoming']['last_update_ts']) > Number(matrix.buddies[partner]['last_outgoing']['last_update_ts']))
    ) {
        return;
    }

    let delta = Date.now() - Number(matrix.buddies[partner]['last_outgoing']['last_update_ts']);
    delta = (delta / 1000).toFixed(2);
    logger.verbose('MatrixUtil: Response time in ' + delta + ' seconds', {partner: partner});
    matrix.buddies[partner]['last_response_time_in_secs'] = delta;
}

MatrixUtil.prototype._updateLastMessage = function(partner, msg, direction) {
    if (!partner) { return; }
    partner = _cleanPartnerId(partner);

    let matrix = this.matrix;
    let logger = this.logger;

    if (!matrix) {
        return;
    }

    if (!matrix.buddies) {
        matrix.buddies = {};
    }

    if (!matrix.buddies[partner]) {
        matrix.buddies[partner] = {};
    }

    if (direction == 'incoming') {
        try {
            this._updateLastResponseTime(partner);
        }
        catch(e) {
            logger.warn('Exception when updateLastResponseTime', {err: e});
        }
    }

    matrix.buddies[partner]['waiting_for_response'] = (direction == 'outgoing');

    matrix.buddies[partner]['last_' + direction] = {
        msg: msg,
        last_update: moment().format(momentFormat),
        last_update_ts: Date.now()
    }

    if (direction == 'outgoing') {
        return;
    }
}

MatrixUtil.prototype.updateLastIncoming = function(partner, msg) {
    this._updateLastMessage(partner, msg, 'incoming');
}

MatrixUtil.prototype.updateLastOutgoing = function(partner, msg) {
    this._updateLastMessage(partner, msg, 'outgoing');
}