<commit_before>
        // lib code
var   state = require('../haxe-state')
    , run   = require('../haxe-call')
    , uuid = require('../utils/uuid')
    , code = require('../utils/haxe-code')
        // node built in
    , path  = require('path')
        // dep code
    , xml2js = require('xml2js')
    , fs = require('fs-extra')

var REGEX_TYPE_PATH_AFTER = /^([\.A-Za-z0-9_]+)/;
var REGEX_TYPE_PATH_BEFORE = /([\.A-Za-z0-9_]+)$/;

module.exports = {

    init: function() {

            // Don't try anything without valid hxml data
        if(!state.hxml_cwd) return;
        if(!state.hxml_content && !state.hxml_file) return;

            // Extract info from XML once, one second after the haxe server is supposed to be started
            // A better option could be to have an explicit event to catch when the server is ready
        var time = atom.config.get('haxe.server_activation_start_delay');
        setTimeout(this._extract_info_from_xml_output.bind(this), (time + 1) * 1000.0);
    },

    dispose: function() {
        this.info = null;
        this.hxml_for_info = null;
    },

    jump: function() {

            // Don't try anything without valid hxml data
        if(!state.hxml_cwd) return;
        if(!state.hxml_content && !state.hxml_file) return;

        var editor = atom.workspace.getActiveTextEditor();
        var file_path = editor.getPath();

        if(!file_path) return;

        var buffer_pos = editor.getLastCursor().getBufferPosition().toArray();
        var pretext = editor.getTextInBufferRange( [ [0,0], buffer_pos] );
        var text = editor.getText();
        var index = pretext.length;
        var posttext = text.slice(index);

            // Extract selected word (composed), if any
        REGEX_TYPE_PATH_BEFORE.lastIndex = -1;
        REGEX_TYPE_PATH_AFTER.lastIndex = -1;
        var word = '';
        var m;
        if (m = pretext.match(REGEX_TYPE_PATH_BEFORE)) {
            word = m[1];
        }
        if (m = posttext.match(REGEX_TYPE_PATH_AFTER)) {
            word += m[1];
        }

            // By checking if word is uppercase or not, we can
            // deduct whether it can be a type or not
        var word_is_uppercase_type = false;
        var word_last_dot_index = -1;
        var last_word_element = word;
        if (word.length > 0) {
            word_last_dot_index = word.lastIndexOf('.');
            if (word_last_dot_index == -1) {
                if (word.charAt(0).toUpperCase() == word.charAt(0)) {
                    word_is_uppercase_type = true;
                }
            }
            else {
                last_word_element = word.slice(word_last_dot_index + 1);
                if (last_word_element.charAt(0) != null
                && last_word_element.charAt(0).toUpperCase() == last_word_element.charAt(0)) {
                    word_is_uppercase_type = true;
                }
            }
        }

            // Extract end of expression
        var expr_end = code.parse_end_of_expression(posttext, 0);
        expr_end = '';

        var imports = code.extract_imports(text);

        var find_declaration = function(word) {

            this._extract_info_from_xml_output().then(function(info) {

                    // Suery haxe to get type
                var port = atom.config.get('haxe.server_port');
                var args = [];
                args.push('--no-output');

                if (port) {
                    args.push('--connect');
                    args.push(String(port));
                }

                args.push('--display');
                args.push(file_path + '@' + Buffer.byteLength(text.slice(0, index + expr_end.length), 'utf8') + '@type');

                args = state.as_args(args);

                run.haxe(args).then(function(result) {

                    xml2js.parseString(result.err, function(err, json) {

                        if (json != null) {
                                // Type got resolved, let's jump!
                            if (json.type != null && info[json.type.trim()] != null) {
                                this._open(info[json.type.trim()]);
                                return;
                            }
                        }
                        else {
                                // No? Then use @toplevel to try to resolve identifier
                            var index_for_toplevel = code.index_of_closest_block(text, index);

                            var args = [];
                            args.push('--no-output');

                            if (port) {
                                args.push('--connect');
                                args.push(String(port));
                            }

                            args.push('--display');
                            args.push(file_path + '@' + Buffer.byteLength(text.slice(0, index_for_toplevel), 'utf8') + '@toplevel');

                            args = state.as_args(args);

                            run.haxe(args).then(function(result) {

                                xml2js.parseString(result.err, function(err, json) {

                                    if (json != null) {
                                        var types = this._parse_toplevel_list(json);

                                        if (types[word] != null && info[types[word].type] != null) {
                                            this._open(info[types[word].type]);
                                            return;
                                        }

                                    }

                                        // Try to extract file from raw word directly
                                    if (info[word] != null) {
                                        this._open(info[word]);
                                        return;
                                    }
                                    else {
                                            // Last hope, play with packages

                                            // Does it come from an alias?
                                        if (imports[word] != null && info[imports[word]] != null) {
                                            this._open(info[imports[word]]);
                                            return;
                                        }

                                            // Does it come from an imported package?
                                        for (var key in imports) {
                                            var imported_type = imports[key] + '.' + last_word_element;
                                            if (info[imported_type] != null) {
                                                this._open(info[imported_type]);
                                                return;
                                            }
                                        }

                                            // Is it a type of a parent package?
                                        var package_name = code.extract_package(text);
                                        var resolved_type;
                                        while (package_name.length > 0) {
                                                // Is it a type in the current package
                                            if (info[package_name + '.' + last_word_element] != null) {
                                                this._open(info[package_name + '.' + last_word_element]);
                                                return;
                                            }

                                            if (package_name.lastIndexOf('.') != -1) {
                                                package_name = package_name.slice(0, package_name.lastIndexOf('.'));
                                            } else {
                                                break;
                                            }
                                        }

                                            // Is there any other package having this type?
                                        for (var key in info) {
                                            if (key.slice(key.length - last_word_element.length - 1) == ('.' + last_word_element)) {
                                                this._open(info[key]);
                                                return;
                                            }
                                        }

                                            // If the word is composed of multiple identifiers, try removing one element
                                        if (word.lastIndexOf('.') != -1) {
                                            find_declaration(word.slice(0, word.lastIndexOf('.')));
                                        }
                                    }

                                }.bind(this));

                            }.bind(this));
                        }

                    }.bind(this));

                }.bind(this));

            }.bind(this));

        }.bind(this);

        find_declaration(word);
    },

    _open: function(info) {
        var file_path;
        var cwd = state.hxml_cwd;

        if (typeof(info) == 'string') {
                // Just open the file
            file_path = info;
            if (!path.isAbsolute(file_path)) {
                file_path = path.join(cwd, file_path);
            }
            atom.workspace.open(info);
        }
        else {
            file_path = info.file;
            if (!path.isAbsolute(file_path)) {
                file_path = path.join(cwd, file_path);
            }

                // Find identifier in content
            var identifier = info.name;
            if (identifier.lastIndexOf('.') != -1) {
                identifier = identifier.slice(identifier.lastIndexOf('.') + 1);
            }

            var contents = String(fs.readFileSync(file_path));
            var cleaned_contents = code.code_with_empty_comments_and_strings(contents);
            var keyword = info.kind;
            if (keyword === 'class') {
                keyword = '(?:class|interface)';
            }
            var regex = new RegExp(keyword + '\\s' + identifier + '[^A-Za-z0-9_]');
            var match;
            if (match = cleaned_contents.match(regex)) {
                var index = cleaned_contents.indexOf(match[0]);
                var lines = contents.slice(0, index).split("\n");
                var line = lines.length - 1;
                var column = lines[lines.length - 1].length + match[0].length - identifier.length - 1;

                    // We found it, let's open at the exact location
                atom.workspace.open(file_path, {
                    initialLine: line,
                    initialColumn: column

                }).then(function() {

                        // Select the identifier
                    var editor = atom.workspace.getActiveTextEditor();
                    editor.selectToEndOfWord();

                        // Workaround to ensure the editor will be centered to the identifier
                        // Without it, the identifier is often too much at the bottom of the screen
                    editor.scrollToBottom();
                    editor.scrollToCursorPosition();

                }.bind(this));

            }
            else {
                atom.workspace.open(file_path);
            }

        }
    },

    _extract_info_from_xml_output: function() {
        return new Promise(function(resolve, reject) {

                // If info is already extracted, just return it
            if (this.info != null) {
                    // Reset info if hxml is different than previous run
                if (this.hxml_for_info != state.hxml_content) {
                    this.info = null;
                } else {
                    return resolve(this.info);
                }
            }

            var port = atom.config.get('haxe.server_port');
            var tmp_path = state.tmp_path;
            var hash = uuid.v1();
            var xml_path = path.join(tmp_path, hash, 'info.xml');

            fs.ensureDirSync(path.dirname(xml_path));

            var args = [];
            args.push('--no-output');

            if (port) {
                args.push('--connect');
                args.push(String(port));
            }

            args.push('-xml');
            args.push(xml_path);

            args = state.as_args(args);

            run.haxe(args).then((function(result) {

                    // Get xml contents
                var content = fs.readFileSync(xml_path);
                    // Remove file
                fs.removeSync(path.dirname(xml_path));

                xml2js.parseString(content, function (err, json) {

                    if (json == null) return resolve({});

                        // Parse xml result
                    var info = {};
                    var kinds = ['abstract', 'class', 'enum', 'typedef'];
                    for (var i = 0; i < kinds.length; i++) {
                        var kind = kinds[i];
                        var kind_entries = json.haxe[kind];
                        for (var j = 0; j < kind_entries.length; j++) {
                            var entry = kind_entries[j];
                            info[entry.$.path] = {
                                kind: kind,
                                file: entry.$.file,
                                module: entry.$.module,
                                name: entry.$.path
                            };
                        }
                    }

                    var has_keys = false;
                    for (var key in info) {
                        has_keys = true;
                        break;
                    }

                    if (has_keys) {
                        this.hxml_for_info = state.hxml_content;
                        this.info = info;
                        resolve(this.info);
                    } else {
                        resolve({});
                    }

                }.bind(this));

            }).bind(this));

        }.bind(this));
    },

    _parse_toplevel_list: function(json) {

        var types = {};

            // If json is not valid, return empty list
        if (json.il == null || json.il.i == null) return types;

        for (var i = 0; i < json.il.i.length; i++) {
            var raw_entry = json.il.i[i];
            var name = raw_entry._;
            if (types[name] == null) {
                types[name] = {
                    type:   (raw_entry.$.t || raw_entry.$.p),
                    kind:   raw_entry.$.k
                };
            }
        }

        return types;
    }

}
<commit_msg>go to declaration; Improve feature, especially for method calls, by using `@position`<commit_after>
        // lib code
var   state = require('../haxe-state')
    , run   = require('../haxe-call')
    , uuid = require('../utils/uuid')
    , code = require('../utils/haxe-code')
    , compiler = require('../parsing/compiler')
        // node built in
    , path  = require('path')
        // dep code
    , xml2js = require('xml2js')
    , fs = require('fs-extra')

var REGEX_TYPE_PATH_AFTER = /^([\.A-Za-z0-9_]+)/;
var REGEX_TYPE_PATH_BEFORE = /([\.A-Za-z0-9_]+)$/;

module.exports = {

    init: function() {

            // Don't try anything without valid hxml data
        if(!state.hxml_cwd) return;
        if(!state.hxml_content && !state.hxml_file) return;

            // Extract info from XML once, one second after the haxe server is supposed to be started
            // A better option could be to have an explicit event to catch when the server is ready
        var time = atom.config.get('haxe.server_activation_start_delay');
        setTimeout(this._extract_info_from_xml_output.bind(this), (time + 1) * 1000.0);
    },

    dispose: function() {
        this.info = null;
        this.hxml_for_info = null;
    },

    jump: function() {

            // Don't try anything without valid hxml data
        if(!state.hxml_cwd) return;
        if(!state.hxml_content && !state.hxml_file) return;

        var editor = atom.workspace.getActiveTextEditor();
        var file_path = editor.getPath();

        if(!file_path) return;

        var buffer_pos = editor.getLastCursor().getBufferPosition().toArray();
        var pretext = editor.getTextInBufferRange( [ [0,0], buffer_pos] );
        var text = editor.getText();
        var index = pretext.length;
        var posttext = text.slice(index);

            // Extract selected word (composed), if any
        REGEX_TYPE_PATH_BEFORE.lastIndex = -1;
        REGEX_TYPE_PATH_AFTER.lastIndex = -1;
        var word = '';
        var m;
        if (m = pretext.match(REGEX_TYPE_PATH_BEFORE)) {
            word = m[1];
        }
        if (m = posttext.match(REGEX_TYPE_PATH_AFTER)) {
            word += m[1];
        }
        word = word.replace(/^\.+/, '').replace(/\.+$/, '');

            // By checking if word is uppercase or not, we can
            // deduct whether it can be a type or not
        var word_is_uppercase_type = false;
        var word_last_dot_index = -1;
        var last_word_element = word;
        if (word.length > 0) {
            word_last_dot_index = word.lastIndexOf('.');
            if (word_last_dot_index == -1) {
                if (word.charAt(0).toUpperCase() == word.charAt(0)) {
                    word_is_uppercase_type = true;
                }
            }
            else {
                last_word_element = word.slice(word_last_dot_index + 1);
                if (last_word_element.charAt(0) != null
                && last_word_element.charAt(0).toUpperCase() == last_word_element.charAt(0)) {
                    word_is_uppercase_type = true;
                }
            }
        }

            // Extract end of expression
        var expr_end = code.parse_end_of_expression(text, index);

        var imports = code.extract_imports(text);

            // First, try to query haxe to get position as this is `the good way` to get our job done.
            // Unfortunately it doesn't work in all cases yet
            // If it fails, we fall back to a combination of compiler XML output and @type/@toplevel information
            // Overall, this rather quick implementation should still work in most common cases
        var find_declaration_from_position = function(word) {

            var display_arg = file_path + '@' + Buffer.byteLength(text.slice(0, index + expr_end.length), 'utf8') + '@position';
            this._run_haxe_display(display_arg).then(function(result) {

                xml2js.parseString(result.err, function(err, json) {

                    if (json != null) {
                            // Position got resolved, let's jump!
                        if (json.list != null && json.list.pos != null && json.list.pos.length > 0) {
                            var parsed_pos = compiler.parse_output(json.list.pos[0], {allow_empty_message: true});
                            if (parsed_pos.length > 0) {
                                parsed_pos = parsed_pos[0];
                                this._open({
                                    file: parsed_pos.file_path,
                                    line: parsed_pos.line,
                                    name: word
                                });
                                return;
                            }
                        }
                    }

                        // Fall back to the XML/@type/@toplevel resolution
                    find_declaration_from_xml(word);

                }.bind(this));

            }.bind(this));

        }.bind(this);

        var find_declaration_from_xml = function(word) {

            this._extract_info_from_xml_output().then(function(info) {

                    // Query haxe to get type
                var display_arg = file_path + '@' + Buffer.byteLength(text.slice(0, index + expr_end.length), 'utf8') + '@type';
                this._run_haxe_display(display_arg).then(function(result) {

                    xml2js.parseString(result.err, function(err, json) {

                        if (json != null) {
                                // Type got resolved, let's jump!
                            if (json.type != null && info[json.type.trim()] != null) {
                                this._open(info[json.type.trim()]);
                                return;
                            }
                        }
                        else {
                                // No? Then use @toplevel to try to resolve identifier
                            var index_for_toplevel = code.index_of_closest_block(text, index);

                            display_arg = file_path + '@' + Buffer.byteLength(text.slice(0, index_for_toplevel), 'utf8') + '@toplevel';
                            this._run_haxe_display(display_arg).then(function(result) {

                                xml2js.parseString(result.err, function(err, json) {

                                    if (json != null) {
                                        var types = this._parse_toplevel_list(json);

                                        if (types[word] != null && info[types[word].type] != null) {
                                            this._open(info[types[word].type]);
                                            return;
                                        }

                                    }

                                        // Try to extract file from raw word directly
                                    if (info[word] != null) {
                                        this._open(info[word]);
                                        return;
                                    }
                                    else {
                                            // Last hope, play with packages

                                            // Does it come from an alias?
                                        if (imports[word] != null && info[imports[word]] != null) {
                                            this._open(info[imports[word]]);
                                            return;
                                        }

                                            // Does it come from an imported package?
                                        for (var key in imports) {
                                            var imported_type = imports[key] + '.' + last_word_element;
                                            if (info[imported_type] != null) {
                                                this._open(info[imported_type]);
                                                return;
                                            }
                                        }

                                            // Is it a type of a parent package?
                                        var package_name = code.extract_package(text);
                                        var resolved_type;
                                        while (package_name.length > 0) {
                                                // Is it a type in the current package
                                            if (info[package_name + '.' + last_word_element] != null) {
                                                this._open(info[package_name + '.' + last_word_element]);
                                                return;
                                            }

                                            if (package_name.lastIndexOf('.') != -1) {
                                                package_name = package_name.slice(0, package_name.lastIndexOf('.'));
                                            } else {
                                                break;
                                            }
                                        }

                                            // Is there any other package having this type?
                                        for (var key in info) {
                                            if (key.slice(key.length - last_word_element.length - 1) == ('.' + last_word_element)) {
                                                this._open(info[key]);
                                                return;
                                            }
                                        }

                                            // If the word is composed of multiple identifiers, try removing one element
                                        if (word.lastIndexOf('.') != -1) {
                                            find_declaration_from_xml(word.slice(0, word.lastIndexOf('.')));
                                        }
                                    }

                                }.bind(this));

                            }.bind(this));
                        }

                    }.bind(this));

                }.bind(this));

            }.bind(this));

        }.bind(this);

            // Start resolution
        find_declaration_from_position(word);
    },

    _open: function(info) {
        var file_path;
        var cwd = state.hxml_cwd;

        if (typeof(info) == 'string') {
                // Just open the file
            file_path = info;
            if (!path.isAbsolute(file_path)) {
                file_path = path.join(cwd, file_path);
            }
            atom.workspace.open(info);
        }
        else {
            file_path = info.file;
            if (!path.isAbsolute(file_path)) {
                file_path = path.join(cwd, file_path);
            }

                // Find identifier in content
            var identifier = info.name;
            if (identifier.lastIndexOf('.') != -1) {
                identifier = identifier.slice(identifier.lastIndexOf('.') + 1);
            }

            var contents = String(fs.readFileSync(file_path));

            if (info.line != null) {
                    // Find the characters in line from identifier
                var lines = contents.slice(0, index).split("\n");
                var target_line = lines[info.line-1];
                var keyword = '(?:function|var|abstract|class|interface|typedef|enum)';

                var regex = new RegExp(keyword + '\\s' + identifier + '[^A-Za-z0-9_]');
                var match;
                if (match = target_line.match(regex)) {
                    var line = info.line - 1;
                    var column = target_line.indexOf(match[0]) + match[0].length - identifier.length - 1;
                        // We found it, let's open at the exact location
                    atom.workspace.open(file_path, {
                        initialLine: line,
                        initialColumn: column

                    }).then(function() {

                            // Select the identifier
                        var editor = atom.workspace.getActiveTextEditor();
                        editor.selectToEndOfWord();

                            // Workaround to ensure the editor will be centered to the identifier
                            // Without it, the identifier is often too much at the bottom of the screen
                        editor.scrollToBottom();
                        editor.scrollToCursorPosition();

                    });
                }
                else {
                    atom.workspace.open(file_path, {
                        initialLine: info.line - 1
                    });
                }
            }
            else {
                var cleaned_contents = code.code_with_empty_comments_and_strings(contents);
                var keyword = info.kind;
                if (keyword === 'class') {
                    keyword = '(?:class|interface)';
                }
                var regex = new RegExp(keyword + '\\s+' + identifier + '[^A-Za-z0-9_]');
                var match;
                if (match = cleaned_contents.match(regex)) {
                    var index = cleaned_contents.indexOf(match[0]);
                    var lines = contents.slice(0, index).split("\n");
                    var line = lines.length - 1;
                    var column = lines[lines.length - 1].length + match[0].length - identifier.length - 1;

                        // We found it, let's open at the exact location
                    atom.workspace.open(file_path, {
                        initialLine: line,
                        initialColumn: column

                    }).then(function() {

                            // Select the identifier
                        var editor = atom.workspace.getActiveTextEditor();
                        editor.selectToEndOfWord();

                            // Workaround to ensure the editor will be centered to the identifier
                            // Without it, the identifier is often too much at the bottom of the screen
                        editor.scrollToBottom();
                        editor.scrollToCursorPosition();

                    }.bind(this));

                }
                else {
                    atom.workspace.open(file_path);
                }
            }

        }
    },

    _extract_info_from_xml_output: function() {
        return new Promise(function(resolve, reject) {

                // If info is already extracted, just return it
            if (this.info != null) {
                    // Reset info if hxml is different than previous run
                if (this.hxml_for_info != state.hxml_content) {
                    this.info = null;
                } else {
                    return resolve(this.info);
                }
            }

            var port = atom.config.get('haxe.server_port');
            var tmp_path = state.tmp_path;
            var hash = uuid.v1();
            var xml_path = path.join(tmp_path, hash, 'info.xml');

            fs.ensureDirSync(path.dirname(xml_path));

            var args = [];
            args.push('--no-output');

            if (port) {
                args.push('--connect');
                args.push(String(port));
            }

            args.push('-xml');
            args.push(xml_path);

            args = state.as_args(args);

            run.haxe(args).then((function(result) {

                    // Get xml contents
                var content = fs.readFileSync(xml_path);
                    // Remove file
                fs.removeSync(path.dirname(xml_path));

                xml2js.parseString(content, function (err, json) {

                    if (json == null) return resolve({});

                        // Parse xml result
                    var info = {};
                    var kinds = ['abstract', 'class', 'enum', 'typedef'];
                    for (var i = 0; i < kinds.length; i++) {
                        var kind = kinds[i];
                        var kind_entries = json.haxe[kind];
                        for (var j = 0; j < kind_entries.length; j++) {
                            var entry = kind_entries[j];
                            info[entry.$.path] = {
                                kind: kind,
                                file: entry.$.file,
                                module: entry.$.module,
                                name: entry.$.path
                            };
                        }
                    }

                    var has_keys = false;
                    for (var key in info) {
                        has_keys = true;
                        break;
                    }

                    if (has_keys) {
                        this.hxml_for_info = state.hxml_content;
                        this.info = info;
                        resolve(this.info);
                    } else {
                        resolve({});
                    }

                }.bind(this));

            }).bind(this));

        }.bind(this));
    },

    _parse_toplevel_list: function(json) {

        var types = {};

            // If json is not valid, return empty list
        if (json.il == null || json.il.i == null) return types;

        for (var i = 0; i < json.il.i.length; i++) {
            var raw_entry = json.il.i[i];
            var name = raw_entry._;
            if (types[name] == null) {
                types[name] = {
                    type:   (raw_entry.$.t || raw_entry.$.p),
                    kind:   raw_entry.$.k
                };
            }
        }

        return types;
    },

        // Query haxe server to display information (type, toplevel, position)
    _run_haxe_display: function(display_arg) {

        var port = atom.config.get('haxe.server_port');
        var args = [];
        args.push('--no-output');

        if (port) {
            args.push('--connect');
            args.push(String(port));
        }

        args.push('--display');
        args.push(display_arg);

        args = state.as_args(args);

        return run.haxe(args);
    }

}
<|endoftext|>


<commit_before>var _ = require('lodash'),
    core = require('./core'),
    Emitter = require('events'),
    inherits = require('inherits'),
    now = require('performance-now'),
    sdk = require('postman-collection'),
    requests = require('./request-wrapper'),

    RESPONSE_START_EVENT_BASE = 'response.start.',
    RESPONSE_END_EVENT_BASE = 'response.end.',

    RESPONSE_START = 'responseStart',
    RESPONSE_END = 'response',

    ERROR_RESTRICTED_ADDRESS = 'NETERR: getaddrinfo ENOTFOUND ',

    /**
     * Headers which get overwritten by the requester.
     *
     * @private
     * @const
     * @type {Object}
     */
    OVERWRITTEN_HEADERS = {
        cookie: true, // cookies get appended with `;`
        referer: true // referer gets updated on redirects
    },

    /**
     * Connection Header, added based on agentOptions.keepAlive
     *
     * @private
     * @const
     * @type {Object}
     */
    CONNECTION_HEADER = {
        key: 'Connection',
        keepAlive: 'keep-alive',
        close: 'close'
    },

    /**
     * Creates a sdk compatible cookie from a tough-cookie compatible cookie.
     *
     * @param cookie
     * @returns {Object}
     */
    toPostmanCookie = function (cookie) {
        cookie.toJSON && (cookie = cookie.toJSON());

        return new sdk.Cookie({
            name: cookie.key,
            value: cookie.value,
            expires: cookie.expires === 'Infinity' ? null : cookie.expires,
            maxAge: cookie.maxAge,
            domain: cookie.domain,
            path: cookie.path,
            secure: cookie.secure,
            httpOnly: cookie.httpOnly,
            hostOnly: cookie.hostOnly,
            extensions: cookie.extensions
        });
    },

    /**
     * This method is used in conjunction with _.transform method to convert multi-value headers to multiple single
     * value headers
     *
     * @param  {Array} acc
     * @param  {Array|String} val
     * @param  {String} key
     * @return {Object}
     */
    transformMultiValueHeaders = function (acc, val, key) {
        var i, ii;

        if (Array.isArray(val)) {
            for (i = 0, ii = val.length; i < ii; i++) {
                acc.push({
                    key: key,
                    value: val[i]
                });
            }
        }
        else {
            acc.push({
                key: key,
                value: val
            });
        }
    },

    /**
     * Calculate request timings offset by adding runtime overhead which
     * helps to determine request prepare and process time taken.
     *
     * @param {Number} runtimeTimer - Runtime request start HR time
     * @param {Number} requestTimer - Request start HR time
     * @param {Object} timings - Request timings offset
     * @returns {Object}
     */
    calcTimingsOffset = function (runtimeTimer, requestTimer, timings) {
        if (!(runtimeTimer && requestTimer && timings)) { return; }

        // runtime + postman-request initialization time
        var initTime = requestTimer - runtimeTimer,
            offset = {
                request: initTime
            };

        // add initialization overhead to request offsets
        _.forOwn(timings, function (value, key) {
            offset[key] = value + initTime;
        });

        // total time taken by runtime to get the response
        // @note if offset.end is missing, that means request is not complete.
        //       this is used to calculate timings on responseStart.
        if (offset.end) {
            offset.done = now() - runtimeTimer;
        }

        return offset;
    },

    Requester;

/**
 * Creates a new Requester, which is used to make HTTP(s) requests.
 *
 * @param trace
 * @param options
 * @param {Boolean} [options.keepAlive=true] Optimizes HTTP connections by keeping them alive, so that new requests
 * to the same host are made over the same underlying TCP connection.
 * @param {CookieJar} [options.cookieJar] A cookie jar to use with Node requests.
 * @param {Boolean} [options.strictSSL]
 * @param {Boolean} [options.followRedirects=true] If false, returns a 301/302 as the response code
 * instead of following the redirect
 * @note `options.keepAlive` is only supported in Node.
 * @note `options.cookieJar` is only supported in Node.
 *
 * @constructor
 */
inherits(Requester = function (trace, options) {
    this.options = options || {};

    // protect the timeout value from being non-numeric or infinite
    if (!_.isFinite(this.options.timeout)) {
        this.options.timeout = undefined;
    }

    this.trace = trace;
    Requester.super_.call(this);
}, Emitter);

_.assign(Requester.prototype, /** @lends Requester.prototype */ {

    /**
     * Perform an HTTP request.
     *
     * @param {String} id
     * @param {Request} request
     * @param {Object} protocolProfileBehavior
     * @param {Function} callback
     */
    request: function (id, request, protocolProfileBehavior, callback) {
        var self = this,
            hostname,
            cookieJar,
            requestOptions,
            networkOptions = self.options.network || {},
            startTime = Date.now(),
            startTimer = now(), // high-resolution time
            cookies = [],
            responseHeaders = [],
            responseJSON = {},

            // keep track of `responseStart` and `response` triggers
            _responseStarted = false,
            _responseEnded = false,
            _responseData = {},

            // Refer: https://github.com/postmanlabs/postman-runtime/blob/v7.14.0/docs/history.md
            getExecutionHistory = function (debugInfo) {
                var history = {
                        execution: {
                            verbose: Boolean(requestOptions.verbose),
                            sessions: {},
                            data: []
                        }
                    },
                    executionData = [],
                    requestSessions = {};

                if (!Array.isArray(debugInfo)) {
                    return history;
                }

                // prepare history from request debug data
                debugInfo.forEach(function (debugData) {
                    if (!debugData) { return; }

                    // cache connection sessions, to be referred later
                    if (debugData.session && !debugData.session.reused) {
                        requestSessions[debugData.session.id] = debugData.session.data;
                    }

                    executionData.push({
                        request: debugData.request,
                        response: debugData.response,
                        timings: debugData.timings && {
                            // runtime start time
                            start: startTime,
                            // request start time
                            requestStart: debugData.timingStart,
                            // offsets calculated are relative to runtime start time
                            offset: calcTimingsOffset(startTimer, debugData.timingStartTimer, debugData.timings)
                        },
                        session: debugData.session && {
                            id: debugData.session.id,
                            // is connection socket reused
                            reused: debugData.session.reused
                        }
                    });
                });

                // update history object
                history.execution.data = executionData;
                history.execution.sessions = requestSessions;

                return history;
            },

            /**
             * Add the missing/system headers in the request object
             *
             * @param {Object[]} headers
             */
            addMissingRequestHeaders = function (headers) {
                var header,
                    keepAlive,
                    lowerCasedKey;

                _.forOwn(headers, function (value, key) {
                    header = request.headers.one(key);
                    lowerCasedKey = _.lowerCase(key);

                    // neither request nor runtime overwrites user-defined headers
                    // so instead of upsert, just add missing headers to the list
                    // with system: true flag.
                    if (!header || header.disabled) {
                        request.headers.add({
                            key: key,
                            value: value,
                            system: true
                        });

                        return;
                    }

                    // bail out if
                    // 1. header is not one of overwritten headers
                    // 2. overwritten header value is not updated
                    if (!(OVERWRITTEN_HEADERS[lowerCasedKey] && value !== header.value)) {
                        return;
                    }

                    // in case of duplicate headers, remove all the duplicates
                    // just keep the last item in the list.
                    if (Array.isArray(_.get(request.headers, ['reference', lowerCasedKey]))) {
                        request.headers.remove(function (h) {
                            return _.lowerCase(h.key) === lowerCasedKey && h !== header;
                        });
                    }

                    // update headers which gets overwritten by the requester
                    header.update({
                        key: key,
                        value: value,
                        system: true
                    });
                });

                keepAlive = _.get(requestOptions, 'agentOptions.keepAlive');

                // @note Connection header is added by Node based on the agent's
                // keepAlive option. Don't add if custom is defined already.
                if (!request.headers.has(CONNECTION_HEADER.key)) {
                    request.headers.add({
                        key: CONNECTION_HEADER.key,
                        value: keepAlive ? CONNECTION_HEADER.keepAlive : CONNECTION_HEADER.close,
                        system: true
                    });
                }
            },

            /**
             * Helper function to trigger `callback` and complete the request function
             *
             * @param {Error} error - error while requesting
             * @param {Response} response - SDK Response instance
             * @param {Object} history - Request-Response History
             */
            onEnd = function (error, response, history) {
                self.emit(RESPONSE_END_EVENT_BASE + id, error, self.trace.cursor,
                    self.trace, response, request, cookies, history);

                return callback(error, response, request, cookies, history);
            },

            /**
             * Helper function to keep track of `responseStart` and `response`
             * triggers to make they are emitted in correct order.
             *
             * @todo fix requester control flow to remove this hack!
             *  this is required because CookieJar.getCookies is async method
             *  and by that time postman-request ends the request, which affects
             *  request post-send helpers because `response.start` event is not
             *  emitted on time and shared variables `cookies`, `responseJSON`,
             *  and, `responseHeaders` are initialized in onStart function.
             *
             * @param {String} trigger - trigger name
             * @param {Response} response - SDK Response instance
             * @param {Object} history - Request-Response History
             */
            onComplete = function (trigger, response, history) {
                if (trigger === RESPONSE_START) {
                    // set flag for responseStart callback
                    _responseStarted = true;

                    // if response is ended, end the response using cached data
                    if (_responseEnded) {
                        onEnd(null, _responseData.response, _responseData.history);
                    }

                    // bail out and wait for response end if not ended already
                    return;
                }

                // if response started, don't wait and end the response
                if (_responseStarted) {
                    onEnd(null, response, history);

                    return;
                }

                // wait for responseStart and cache response callback data
                _responseEnded = true;
                _responseData = {
                    response: response,
                    history: history
                };
            },

            /**
             * Helper function to trigger `responseStart` callback and
             * - transform postman-request response instance to SDK Response
             * - filter cookies
             * - filter response headers
             * - add missing request headers
             *
             * @param {Object} response - Postman-Request response instance
             */
            onStart = function (response) {
                var responseStartEventName = RESPONSE_START_EVENT_BASE + id,
                    sdkResponse,
                    history,
                    done = function () {
                        // emit the response.start event which eventually
                        // triggers responseStart callback
                        self.emit(responseStartEventName, null, sdkResponse, request, cookies, history);

                        // trigger completion of responseStart
                        onComplete(RESPONSE_START);
                    };

                // @todo get rid of jsonifyResponse
                responseJSON = core.jsonifyResponse(response, requestOptions);

                // transform response headers to SDK compatible HeaderList
                responseHeaders = _.transform(responseJSON.headers, transformMultiValueHeaders, []);

                // initialize SDK Response instance
                sdkResponse = new sdk.Response({
                    status: response && response.statusMessage,
                    code: responseJSON.statusCode,
                    header: responseHeaders
                });

                // add missing request headers so that they get bubbled up into the UI
                addMissingRequestHeaders(responseJSON.request && responseJSON.request.headers);

                // prepare history from request debug data
                history = getExecutionHistory(_.get(response, 'request._debug'));

                // Pull out cookies from the cookie jar, and make them chrome compatible.
                if (cookieJar && _.isFunction(cookieJar.getCookies)) {
                    cookieJar.getCookies(requestOptions.url, function (err, cookiesFromJar) {
                        if (err) {
                            return done();
                        }

                        cookies = _.transform(cookiesFromJar, function (acc, cookie) {
                            acc.push(toPostmanCookie(cookie));
                        }, []);

                        cookies = new sdk.CookieList(null, cookies);

                        done();
                    });
                }
                else {
                    cookies = new sdk.CookieList(null, []);
                    done();
                }
            };

        // at this point the request could have come from collection, auth or sandbox
        // we can't trust the integrity of this request
        // bail out if request url is empty
        if (!(request && request.url && request.url.toString && request.url.toString())) {
            return onEnd(new Error('runtime:extenstions~request: request url is empty'));
        }

        cookieJar = self.options.cookieJar;
        requestOptions = core.getRequestOptions(request, self.options, protocolProfileBehavior);
        hostname = request.url.getHost();

        // check if host is on the `restrictedAddresses`
        if (networkOptions.restrictedAddresses && core.isAddressRestricted(hostname, networkOptions)) {
            return onEnd(new Error(ERROR_RESTRICTED_ADDRESS + hostname));
        }

        return requests(request, requestOptions, onStart, function (err, res, resBody, debug) {
            if (err) {
                // bubble up http errors
                // @todo - Should we send an empty sdk Response here?
                return onEnd(err);
            }

            var responseTime,
                response,
                history;

            // Calculate the time taken for us to get the response.
            responseTime = Date.now() - startTime;

            if (res && res.timings) {
                // update response time to actual response end time
                // of the final request in the redirect chain.
                responseTime = Math.ceil(res.timings.end);
            }

            // prepare history from request debug data
            history = getExecutionHistory(debug);

            // Response in the SDK format
            // @todo reuse same response instance used for responseStart callback
            response = new sdk.Response({
                code: responseJSON.statusCode,
                status: res && res.statusMessage,
                header: responseHeaders,
                stream: resBody,
                responseTime: responseTime
            });

            onComplete(RESPONSE_END, response, history);
        });
    },

    /**
     * Removes all current event listeners on the requester, and makes it ready for garbage collection :).
     *
     * @param {Function=} cb - Optional callback to be called on disposal
     *
     * @todo - In the future, when the requester manages its own connections etc, close them all here.
     */
    dispose: function (cb) {
        // This is safe for us, because we do not use wait on events. (i.e, no part of Runtime ever waits on
        // any event to occur). We rely on callbacks for that, only choosing to use events as a way of streaming
        // information outside runtime.
        this.removeAllListeners();

        _.isFunction(cb) && cb();
    }
});

_.assign(Requester, /** @lends Requester */ {
    /**
     * Asynchronously create a new requester.
     *
     * @param trace
     * @param trace.type - type of requester to return (for now, just http)
     * @param trace.source - information about who needs this requester, e.g Auth, etc.
     * @param trace.cursor - the cursor
     * @param options
     * @param callback
     * @returns {*}
     */
    create: function (trace, options, callback) {
        return callback(null, new Requester(trace, options));
    }
});

module.exports.Requester = Requester;
<commit_msg>Add reused session data to history object<commit_after>var _ = require('lodash'),
    core = require('./core'),
    Emitter = require('events'),
    inherits = require('inherits'),
    now = require('performance-now'),
    sdk = require('postman-collection'),
    requests = require('./request-wrapper'),

    RESPONSE_START_EVENT_BASE = 'response.start.',
    RESPONSE_END_EVENT_BASE = 'response.end.',

    RESPONSE_START = 'responseStart',
    RESPONSE_END = 'response',

    ERROR_RESTRICTED_ADDRESS = 'NETERR: getaddrinfo ENOTFOUND ',

    /**
     * Headers which get overwritten by the requester.
     *
     * @private
     * @const
     * @type {Object}
     */
    OVERWRITTEN_HEADERS = {
        cookie: true, // cookies get appended with `;`
        referer: true // referer gets updated on redirects
    },

    /**
     * Connection Header, added based on agentOptions.keepAlive
     *
     * @private
     * @const
     * @type {Object}
     */
    CONNECTION_HEADER = {
        key: 'Connection',
        keepAlive: 'keep-alive',
        close: 'close'
    },

    /**
     * Creates a sdk compatible cookie from a tough-cookie compatible cookie.
     *
     * @param cookie
     * @returns {Object}
     */
    toPostmanCookie = function (cookie) {
        cookie.toJSON && (cookie = cookie.toJSON());

        return new sdk.Cookie({
            name: cookie.key,
            value: cookie.value,
            expires: cookie.expires === 'Infinity' ? null : cookie.expires,
            maxAge: cookie.maxAge,
            domain: cookie.domain,
            path: cookie.path,
            secure: cookie.secure,
            httpOnly: cookie.httpOnly,
            hostOnly: cookie.hostOnly,
            extensions: cookie.extensions
        });
    },

    /**
     * This method is used in conjunction with _.transform method to convert multi-value headers to multiple single
     * value headers
     *
     * @param  {Array} acc
     * @param  {Array|String} val
     * @param  {String} key
     * @return {Object}
     */
    transformMultiValueHeaders = function (acc, val, key) {
        var i, ii;

        if (Array.isArray(val)) {
            for (i = 0, ii = val.length; i < ii; i++) {
                acc.push({
                    key: key,
                    value: val[i]
                });
            }
        }
        else {
            acc.push({
                key: key,
                value: val
            });
        }
    },

    /**
     * Calculate request timings offset by adding runtime overhead which
     * helps to determine request prepare and process time taken.
     *
     * @param {Number} runtimeTimer - Runtime request start HR time
     * @param {Number} requestTimer - Request start HR time
     * @param {Object} timings - Request timings offset
     * @returns {Object}
     */
    calcTimingsOffset = function (runtimeTimer, requestTimer, timings) {
        if (!(runtimeTimer && requestTimer && timings)) { return; }

        // runtime + postman-request initialization time
        var initTime = requestTimer - runtimeTimer,
            offset = {
                request: initTime
            };

        // add initialization overhead to request offsets
        _.forOwn(timings, function (value, key) {
            offset[key] = value + initTime;
        });

        // total time taken by runtime to get the response
        // @note if offset.end is missing, that means request is not complete.
        //       this is used to calculate timings on responseStart.
        if (offset.end) {
            offset.done = now() - runtimeTimer;
        }

        return offset;
    },

    Requester;

/**
 * Creates a new Requester, which is used to make HTTP(s) requests.
 *
 * @param trace
 * @param options
 * @param {Boolean} [options.keepAlive=true] Optimizes HTTP connections by keeping them alive, so that new requests
 * to the same host are made over the same underlying TCP connection.
 * @param {CookieJar} [options.cookieJar] A cookie jar to use with Node requests.
 * @param {Boolean} [options.strictSSL]
 * @param {Boolean} [options.followRedirects=true] If false, returns a 301/302 as the response code
 * instead of following the redirect
 * @note `options.keepAlive` is only supported in Node.
 * @note `options.cookieJar` is only supported in Node.
 *
 * @constructor
 */
inherits(Requester = function (trace, options) {
    this.options = options || {};

    // protect the timeout value from being non-numeric or infinite
    if (!_.isFinite(this.options.timeout)) {
        this.options.timeout = undefined;
    }

    this.trace = trace;
    Requester.super_.call(this);
}, Emitter);

_.assign(Requester.prototype, /** @lends Requester.prototype */ {

    /**
     * Perform an HTTP request.
     *
     * @param {String} id
     * @param {Request} request
     * @param {Object} protocolProfileBehavior
     * @param {Function} callback
     */
    request: function (id, request, protocolProfileBehavior, callback) {
        var self = this,
            hostname,
            cookieJar,
            requestOptions,
            networkOptions = self.options.network || {},
            startTime = Date.now(),
            startTimer = now(), // high-resolution time
            cookies = [],
            responseHeaders = [],
            responseJSON = {},

            // keep track of `responseStart` and `response` triggers
            _responseStarted = false,
            _responseEnded = false,
            _responseData = {},

            // Refer: https://github.com/postmanlabs/postman-runtime/blob/v7.14.0/docs/history.md
            getExecutionHistory = function (debugInfo) {
                var history = {
                        execution: {
                            verbose: Boolean(requestOptions.verbose),
                            sessions: {},
                            data: []
                        }
                    },
                    executionData = [],
                    requestSessions = {};

                if (!Array.isArray(debugInfo)) {
                    return history;
                }

                // prepare history from request debug data
                debugInfo.forEach(function (debugData) {
                    if (!debugData) { return; }

                    // @todo cache connection sessions and fetch reused session
                    // from the requester pool.
                    if (debugData.session && !requestSessions[debugData.session.id]) {
                        requestSessions[debugData.session.id] = debugData.session.data;
                    }

                    executionData.push({
                        request: debugData.request,
                        response: debugData.response,
                        timings: debugData.timings && {
                            // runtime start time
                            start: startTime,
                            // request start time
                            requestStart: debugData.timingStart,
                            // offsets calculated are relative to runtime start time
                            offset: calcTimingsOffset(startTimer, debugData.timingStartTimer, debugData.timings)
                        },
                        session: debugData.session && {
                            id: debugData.session.id,
                            // is connection socket reused
                            reused: debugData.session.reused
                        }
                    });
                });

                // update history object
                history.execution.data = executionData;
                history.execution.sessions = requestSessions;

                return history;
            },

            /**
             * Add the missing/system headers in the request object
             *
             * @param {Object[]} headers
             */
            addMissingRequestHeaders = function (headers) {
                var header,
                    keepAlive,
                    lowerCasedKey;

                _.forOwn(headers, function (value, key) {
                    header = request.headers.one(key);
                    lowerCasedKey = _.lowerCase(key);

                    // neither request nor runtime overwrites user-defined headers
                    // so instead of upsert, just add missing headers to the list
                    // with system: true flag.
                    if (!header || header.disabled) {
                        request.headers.add({
                            key: key,
                            value: value,
                            system: true
                        });

                        return;
                    }

                    // bail out if
                    // 1. header is not one of overwritten headers
                    // 2. overwritten header value is not updated
                    if (!(OVERWRITTEN_HEADERS[lowerCasedKey] && value !== header.value)) {
                        return;
                    }

                    // in case of duplicate headers, remove all the duplicates
                    // just keep the last item in the list.
                    if (Array.isArray(_.get(request.headers, ['reference', lowerCasedKey]))) {
                        request.headers.remove(function (h) {
                            return _.lowerCase(h.key) === lowerCasedKey && h !== header;
                        });
                    }

                    // update headers which gets overwritten by the requester
                    header.update({
                        key: key,
                        value: value,
                        system: true
                    });
                });

                keepAlive = _.get(requestOptions, 'agentOptions.keepAlive');

                // @note Connection header is added by Node based on the agent's
                // keepAlive option. Don't add if custom is defined already.
                if (!request.headers.has(CONNECTION_HEADER.key)) {
                    request.headers.add({
                        key: CONNECTION_HEADER.key,
                        value: keepAlive ? CONNECTION_HEADER.keepAlive : CONNECTION_HEADER.close,
                        system: true
                    });
                }
            },

            /**
             * Helper function to trigger `callback` and complete the request function
             *
             * @param {Error} error - error while requesting
             * @param {Response} response - SDK Response instance
             * @param {Object} history - Request-Response History
             */
            onEnd = function (error, response, history) {
                self.emit(RESPONSE_END_EVENT_BASE + id, error, self.trace.cursor,
                    self.trace, response, request, cookies, history);

                return callback(error, response, request, cookies, history);
            },

            /**
             * Helper function to keep track of `responseStart` and `response`
             * triggers to make they are emitted in correct order.
             *
             * @todo fix requester control flow to remove this hack!
             *  this is required because CookieJar.getCookies is async method
             *  and by that time postman-request ends the request, which affects
             *  request post-send helpers because `response.start` event is not
             *  emitted on time and shared variables `cookies`, `responseJSON`,
             *  and, `responseHeaders` are initialized in onStart function.
             *
             * @param {String} trigger - trigger name
             * @param {Response} response - SDK Response instance
             * @param {Object} history - Request-Response History
             */
            onComplete = function (trigger, response, history) {
                if (trigger === RESPONSE_START) {
                    // set flag for responseStart callback
                    _responseStarted = true;

                    // if response is ended, end the response using cached data
                    if (_responseEnded) {
                        onEnd(null, _responseData.response, _responseData.history);
                    }

                    // bail out and wait for response end if not ended already
                    return;
                }

                // if response started, don't wait and end the response
                if (_responseStarted) {
                    onEnd(null, response, history);

                    return;
                }

                // wait for responseStart and cache response callback data
                _responseEnded = true;
                _responseData = {
                    response: response,
                    history: history
                };
            },

            /**
             * Helper function to trigger `responseStart` callback and
             * - transform postman-request response instance to SDK Response
             * - filter cookies
             * - filter response headers
             * - add missing request headers
             *
             * @param {Object} response - Postman-Request response instance
             */
            onStart = function (response) {
                var responseStartEventName = RESPONSE_START_EVENT_BASE + id,
                    sdkResponse,
                    history,
                    done = function () {
                        // emit the response.start event which eventually
                        // triggers responseStart callback
                        self.emit(responseStartEventName, null, sdkResponse, request, cookies, history);

                        // trigger completion of responseStart
                        onComplete(RESPONSE_START);
                    };

                // @todo get rid of jsonifyResponse
                responseJSON = core.jsonifyResponse(response, requestOptions);

                // transform response headers to SDK compatible HeaderList
                responseHeaders = _.transform(responseJSON.headers, transformMultiValueHeaders, []);

                // initialize SDK Response instance
                sdkResponse = new sdk.Response({
                    status: response && response.statusMessage,
                    code: responseJSON.statusCode,
                    header: responseHeaders
                });

                // add missing request headers so that they get bubbled up into the UI
                addMissingRequestHeaders(responseJSON.request && responseJSON.request.headers);

                // prepare history from request debug data
                history = getExecutionHistory(_.get(response, 'request._debug'));

                // Pull out cookies from the cookie jar, and make them chrome compatible.
                if (cookieJar && _.isFunction(cookieJar.getCookies)) {
                    cookieJar.getCookies(requestOptions.url, function (err, cookiesFromJar) {
                        if (err) {
                            return done();
                        }

                        cookies = _.transform(cookiesFromJar, function (acc, cookie) {
                            acc.push(toPostmanCookie(cookie));
                        }, []);

                        cookies = new sdk.CookieList(null, cookies);

                        done();
                    });
                }
                else {
                    cookies = new sdk.CookieList(null, []);
                    done();
                }
            };

        // at this point the request could have come from collection, auth or sandbox
        // we can't trust the integrity of this request
        // bail out if request url is empty
        if (!(request && request.url && request.url.toString && request.url.toString())) {
            return onEnd(new Error('runtime:extenstions~request: request url is empty'));
        }

        cookieJar = self.options.cookieJar;
        requestOptions = core.getRequestOptions(request, self.options, protocolProfileBehavior);
        hostname = request.url.getHost();

        // check if host is on the `restrictedAddresses`
        if (networkOptions.restrictedAddresses && core.isAddressRestricted(hostname, networkOptions)) {
            return onEnd(new Error(ERROR_RESTRICTED_ADDRESS + hostname));
        }

        return requests(request, requestOptions, onStart, function (err, res, resBody, debug) {
            if (err) {
                // bubble up http errors
                // @todo - Should we send an empty sdk Response here?
                return onEnd(err);
            }

            var responseTime,
                response,
                history;

            // Calculate the time taken for us to get the response.
            responseTime = Date.now() - startTime;

            if (res && res.timings) {
                // update response time to actual response end time
                // of the final request in the redirect chain.
                responseTime = Math.ceil(res.timings.end);
            }

            // prepare history from request debug data
            history = getExecutionHistory(debug);

            // Response in the SDK format
            // @todo reuse same response instance used for responseStart callback
            response = new sdk.Response({
                code: responseJSON.statusCode,
                status: res && res.statusMessage,
                header: responseHeaders,
                stream: resBody,
                responseTime: responseTime
            });

            onComplete(RESPONSE_END, response, history);
        });
    },

    /**
     * Removes all current event listeners on the requester, and makes it ready for garbage collection :).
     *
     * @param {Function=} cb - Optional callback to be called on disposal
     *
     * @todo - In the future, when the requester manages its own connections etc, close them all here.
     */
    dispose: function (cb) {
        // This is safe for us, because we do not use wait on events. (i.e, no part of Runtime ever waits on
        // any event to occur). We rely on callbacks for that, only choosing to use events as a way of streaming
        // information outside runtime.
        this.removeAllListeners();

        _.isFunction(cb) && cb();
    }
});

_.assign(Requester, /** @lends Requester */ {
    /**
     * Asynchronously create a new requester.
     *
     * @param trace
     * @param trace.type - type of requester to return (for now, just http)
     * @param trace.source - information about who needs this requester, e.g Auth, etc.
     * @param trace.cursor - the cursor
     * @param options
     * @param callback
     * @returns {*}
     */
    create: function (trace, options, callback) {
        return callback(null, new Requester(trace, options));
    }
});

module.exports.Requester = Requester;
<|endoftext|>


<commit_before>'use strict';
const EventEmitter = require('events').EventEmitter;
const Bucket = require('./Bucket');

/* Example Default Options */
const defaultOptions = {
  bucketSpan: 1000,
  bucketNum: 60,
  percentiles: [0.0, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.995, 1],
  statInterval: 1200
};

class Stats extends EventEmitter {

  constructor(opts) {
    super();
    this._opts = Object.assign({}, defaultOptions, opts);
    this._activePosition = this._opts.bucketNum - 1;

    // initialize buckets
    this._buckets = [];
    for (let i = 0; i < this._opts.bucketNum; i++) {
      this._buckets.push(new Bucket());
    }
    this._activeBucket = this._buckets[this._activePosition];
    this._startBucketSpinning();
    this._totals = this._generateStats(this._buckets, true);
  }

  reset() {
    for (let i = 0; i < this._opts.bucketNum; i++) {
      this._shiftAndPush(this._buckets, new Bucket());
    }
    this._activeBucket = this._buckets[this._activePosition];
    this._update();
  }

  /* Starts cycling through buckets */
  _startBucketSpinning() {
    this._spinningInterval = setInterval(() => {
      this._shiftAndPush(this._buckets, new Bucket());
      this._activeBucket = this._buckets[this._activePosition];
    }, this._opts.bucketSpan);
    this._spinningInterval.unref();
  }

  /* Stop Bucket from spinning */
  _stopBucketSpinning() {
    if (this._spinningInterval) {
      clearInterval(this._spinningInterval);
      this._spinningInterval = undefined;
      return true;
    }
    return false;
  }

  /* Starts cycling through buckets */
  startSnapshots(interval) {
    this._snapshotInterval = setInterval(
      () => {
        this._snapshot();
      },
      interval || this._opts.statInterval);
    this._snapshotInterval.unref();
  }

  /* Starts cycling through buckets */
  stopSnapshots() {
    if (this._snapshotInterval) {
      clearInterval(this._snapshotInterval);
      this._snapshotInterval = undefined;
      return true;
    }
    return false;
  }

  /*
  Generate new totals
  `includeLatencyStats` flag determines whether or not to calculate a new round of
  percentiles. If `includeLatencyStats` is set to false or undefined, the existing
  calculated percentiles will be preserved.
  */
  _generateStats(buckets, includeLatencyStats) {
    // reduce buckets
    const tempTotals = buckets.reduce((prev, cur) => {
      if (!cur) return prev;

      // aggregrate incremented stats
      prev.total += cur.total || 0;
      prev.failed += cur.failed || 0;
      prev.timedOut += cur.timedOut || 0;
      prev.successful += cur.successful || 0;
      prev.shortCircuited += cur.shortCircuited || 0;

      // concat `requestTimes` Arrays
      if (includeLatencyStats) {
        prev.requestTimes.push.apply(prev.requestTimes, cur.requestTimes || []);
      }
      return prev;
    }, {
      failed: 0,
      timedOut: 0,
      total: 0,
      shortCircuited: 0,
      latencyMean: 0,
      successful: 0,
      requestTimes: [],
      percentiles: {}
    });

    // calculate percentiles
    if (includeLatencyStats) {
      tempTotals.requestTimes.sort((a, b) => a - b);
      tempTotals.latencyMean = this._calculateMean(tempTotals.requestTimes) || 0;
      this._opts.percentiles.forEach((p) => {
        tempTotals.percentiles[p] =
          this._calculatePercentile(p, tempTotals.requestTimes) || 0;
      });
    }
    else {
      // pass through previous percentile and mean
      tempTotals.latencyMean = this._totals.latencyMean;
      tempTotals.percentiles = this._totals.percentiles;
    }

    // remove large totals Arrays
    delete tempTotals.requestTimes;
    this._totals = tempTotals;

    return this._totals;
  }

  /*
  Calculate percentile.
  This function assumes the list you are giving it is already ordered.
  */
  _calculatePercentile(percentile, array) {
    if (percentile === 0) {
      return array[0];
    }
    const idx = Math.ceil(percentile * array.length);
    return array[idx - 1];
  }

  /*
  Calculate mean.
  */
  _calculateMean(array) {
    const sum = array.reduce((a, b) => a + b, 0);
    return Math.round(sum / array.length);
  }

  /* Update totals and send updated event */
  _update() {
    this.emit('update', this._generateStats(this._buckets));
  }

  _shiftAndPush(arr, item) {
    arr.push(item);
    arr.shift();
    return arr;
  }

  /* Send snapshot stats event */
  _snapshot() {
    this.emit('snapshot', this._generateStats(this._buckets, true));
  }

  /* Register a failure */
  failure(runTime) {
    this._activeBucket.failure(runTime);
    this._update();
  }

  /* Register a success */
  success(runTime) {
    this._activeBucket.success(runTime);
    this._update();
  }

  /* Register a short circuit */
  shortCircuit() {
    this._activeBucket.shortCircuit();
    this._update();
  }

  /* Register a timeout */
  timeout(runTime) {
    this._activeBucket.timeout(runTime);
    this._update();
  }

}

module.exports = Stats;
<commit_msg>fixed mislabeled function<commit_after>'use strict';
const EventEmitter = require('events').EventEmitter;
const Bucket = require('./Bucket');

/* Example Default Options */
const defaultOptions = {
  bucketSpan: 1000,
  bucketNum: 60,
  percentiles: [0.0, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99, 0.995, 1],
  statInterval: 1200
};

class Stats extends EventEmitter {

  constructor(opts) {
    super();
    this._opts = Object.assign({}, defaultOptions, opts);
    this._activePosition = this._opts.bucketNum - 1;

    // initialize buckets
    this._buckets = [];
    for (let i = 0; i < this._opts.bucketNum; i++) {
      this._buckets.push(new Bucket());
    }
    this._activeBucket = this._buckets[this._activePosition];
    this._startBucketSpinning();
    this._totals = this._generateStats(this._buckets, true);
  }

  reset() {
    for (let i = 0; i < this._opts.bucketNum; i++) {
      this._shiftAndPush(this._buckets, new Bucket());
    }
    this._activeBucket = this._buckets[this._activePosition];
    this._update();
  }

  /* Starts cycling through buckets */
  _startBucketSpinning() {
    this._spinningInterval = setInterval(() => {
      this._shiftAndPush(this._buckets, new Bucket());
      this._activeBucket = this._buckets[this._activePosition];
    }, this._opts.bucketSpan);
    this._spinningInterval.unref();
  }

  /* Stop Bucket from spinning */
  _stopBucketSpinning() {
    if (this._spinningInterval) {
      clearInterval(this._spinningInterval);
      this._spinningInterval = undefined;
      return true;
    }
    return false;
  }

  /* start generating snapshots */
  startSnapshots(interval) {
    this._snapshotInterval = setInterval(
      () => {
        this._snapshot();
      },
      interval || this._opts.statInterval);
    this._snapshotInterval.unref();
  }

  /* stop generating snapshots */
  stopSnapshots() {
    if (this._snapshotInterval) {
      clearInterval(this._snapshotInterval);
      this._snapshotInterval = undefined;
      return true;
    }
    return false;
  }

  /*
  Generate new totals
  `includeLatencyStats` flag determines whether or not to calculate a new round of
  percentiles. If `includeLatencyStats` is set to false or undefined, the existing
  calculated percentiles will be preserved.
  */
  _generateStats(buckets, includeLatencyStats) {
    // reduce buckets
    const tempTotals = buckets.reduce((prev, cur) => {
      if (!cur) return prev;

      // aggregrate incremented stats
      prev.total += cur.total || 0;
      prev.failed += cur.failed || 0;
      prev.timedOut += cur.timedOut || 0;
      prev.successful += cur.successful || 0;
      prev.shortCircuited += cur.shortCircuited || 0;

      // concat `requestTimes` Arrays
      if (includeLatencyStats) {
        prev.requestTimes.push.apply(prev.requestTimes, cur.requestTimes || []);
      }
      return prev;
    }, {
      failed: 0,
      timedOut: 0,
      total: 0,
      shortCircuited: 0,
      latencyMean: 0,
      successful: 0,
      requestTimes: [],
      percentiles: {}
    });

    // calculate percentiles
    if (includeLatencyStats) {
      tempTotals.requestTimes.sort((a, b) => a - b);
      tempTotals.latencyMean = this._calculateMean(tempTotals.requestTimes) || 0;
      this._opts.percentiles.forEach((p) => {
        tempTotals.percentiles[p] =
          this._calculatePercentile(p, tempTotals.requestTimes) || 0;
      });
    }
    else {
      // pass through previous percentile and mean
      tempTotals.latencyMean = this._totals.latencyMean;
      tempTotals.percentiles = this._totals.percentiles;
    }

    // remove large totals Arrays
    delete tempTotals.requestTimes;
    this._totals = tempTotals;

    return this._totals;
  }

  /*
  Calculate percentile.
  This function assumes the list you are giving it is already ordered.
  */
  _calculatePercentile(percentile, array) {
    if (percentile === 0) {
      return array[0];
    }
    const idx = Math.ceil(percentile * array.length);
    return array[idx - 1];
  }

  /*
  Calculate mean.
  */
  _calculateMean(array) {
    const sum = array.reduce((a, b) => a + b, 0);
    return Math.round(sum / array.length);
  }

  /* Update totals and send updated event */
  _update() {
    this.emit('update', this._generateStats(this._buckets));
  }

  _shiftAndPush(arr, item) {
    arr.push(item);
    arr.shift();
    return arr;
  }

  /* Send snapshot stats event */
  _snapshot() {
    this.emit('snapshot', this._generateStats(this._buckets, true));
  }

  /* Register a failure */
  failure(runTime) {
    this._activeBucket.failure(runTime);
    this._update();
  }

  /* Register a success */
  success(runTime) {
    this._activeBucket.success(runTime);
    this._update();
  }

  /* Register a short circuit */
  shortCircuit() {
    this._activeBucket.shortCircuit();
    this._update();
  }

  /* Register a timeout */
  timeout(runTime) {
    this._activeBucket.timeout(runTime);
    this._update();
  }

}

module.exports = Stats;
<|endoftext|>


<commit_before>var Botkit = require(__dirname + '/CoreBot.js');
var express = require('express');
var bodyParser = require('body-parser');
var querystring = require('querystring');
var request = require('request');

function TeamsBot(configuration) {
  var controller = Botkit(configuration || {});

  var token = null;

  request(
    {
      uri: 'https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token',
      form: {
        grant_type: 'client_credentials',
        client_id: configuration.client_id,
        client_secret: configuration.client_secret,
        scope: 'https://api.botframework.com/.default'
      }
    },
    function(err, res, body) {
      var json = JSON.parse(body);
      token = json.access_token;
      console.log('TOKEN', token);
    }
  );

  controller.defineBot(function(botkit, config) {
    var bot = {
      type: 'teams',
      botkit: botkit,
      config: config || {},
      utterances: botkit.utterances
    };

    bot.startConversation = function(message, cb) {
      botkit.startConversation(this, message, cb);
    };

    bot.createConversation = function(message, cb) {
      botkit.createConversation(this, message, cb);
    };

    bot.openConvo = function(src, members, cb) {
        var serviceUrl = src.serviceUrl + 'v3/conversations';
        var data = {
          isGroup: true,
          bot: src.recipient,
          members: members,
          channelData: src.channelData,
          // title: 'new convo title',
        };

        console.log('MAKING REQUEST:');
        console.log(serviceUrl, data);

        request(
          {
            method: 'POST',
            json: true,
            headers: {
              'content-type': 'application/json',
              Authorization: 'Bearer ' + token
            },
            body: data,
            uri: serviceUrl
          },
          function(err, res, body) {
            if (body.error) {
              cb(body.error);
            } else {
              cb(err, body);
            }
          }
        );
    };

    bot.send = function(message, cb) {
      var serviceUrl = message.serviceUrl;

      var serviceUrl =
        serviceUrl +
        '/v3/conversations/' +
        message.conversation.id +
        '/activities';

      var data = {
        type: 'message',
        recipient: message.recipient,
        from: message.from,
      //  conversation: message.conversation,
        // textFormat: 'string',
        text: message.text,
        attachments: message.attachments,
        attachmentLayout: message.attachmentLayout || 'list',
      };

      console.log('REQUEST TO',serviceUrl);

      request(
        {
          method: 'POST',
          json: true,
          headers: {
            'content-type': 'application/json',
            Authorization: 'Bearer ' + token
          },
          body: data,
          uri: serviceUrl
        },
        function(err, res, body) {

          if (err && cb) {
            return cb(er);
          }
          if (!body) {
            if (cb) { return cb('Error parsing json response'); }
          }

          if (body.error) {
            if (cb) { return cb(body.error) }
          }

          if (cb) { cb(null, body); }

        }
      );
    };

    bot.replyToComposeExtension = function(src, resp, cb){
      // console.log('src: ', src);
      src.http_res.send(resp)
      if(cb){
        cb();
      }
    }

    bot.replyInThread = function(src, resp, cb) {

      // make sure this does NOT include the activity id
      src.original_message.conversation = src.channelData.channel;

      bot.reply(src, resp, cb);

    }

    bot.reply = function(src, resp, cb) {
      if(src.type === 'composeExtension'){
        bot.replyToComposeExtension(src, resp, cb)
      }
      if (typeof resp == 'string') {
        resp = {
          text: resp
        };
      }

      resp.serviceUrl = src.original_message.serviceUrl;
      resp.from = src.original_message.recipient;
      resp.recipient = src.original_message.from;
      resp.channel = src.channel;
      resp.conversation = src.original_message.conversation;

      bot.say(resp, cb);
    };

    bot.findConversation = function(message, cb) {
      botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
      for (var t = 0; t < botkit.tasks.length; t++) {
        for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
          if (
            botkit.tasks[t].convos[c].isActive() &&
            botkit.tasks[t].convos[c].source_message.user == message.user
          ) {
            botkit.debug('FOUND EXISTING CONVO!');
            cb(botkit.tasks[t].convos[c]);
            return;
          }
        }
      }

      cb();
    };
    return bot;
  });

  controller.setupWebserver = function(port, cb) {
    if (!port) {
      throw new Error('Cannot start webserver without a port');
    }
    if (isNaN(port)) {
      throw new Error('Specified port is not a valid number');
    }

    var static_dir = process.cwd() + '/public';

    if (
      controller.config &&
      controller.config.webserver &&
      controller.config.webserver.static_dir
    )
      static_dir = controller.config.webserver.static_dir;

    controller.config.port = port;

    controller.webserver = express();
    controller.webserver.use(bodyParser.json());
    controller.webserver.use(bodyParser.urlencoded({ extended: true }));
    controller.webserver.use(express.static(static_dir));

    var server = controller.webserver.listen(
      controller.config.port,
      controller.config.hostname,
      function() {
        controller.log(
          '** Starting webserver on port ' + controller.config.port
        );
        if (cb) {
          cb(null, controller.webserver);
        }
      }
    );

    return controller;
  };

  controller.configureIncomingWebhook = function() {
    controller.webserver.post('/teams/receive', function(req, res) {

      var bot = controller.spawn({});
      var message = req.body;
      bot.serviceUrl = message.serviceUrl;

      controller.ingest(bot, message, res);

    });
  };

  controller.middleware.ingest.use(function(bot, message, res, next) {

      res.status(200);
      if (message.name != 'composeExtension/query') {
        // send a result back immediately
        res.send('');
      }

      message.http_res = res;
      next();

  });

  controller.middleware.normalize.use(function(bot, message, next) {

    message.user = message.from.id;
    message.channel = message.conversation.id;

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next){

    if (message.type === 'invoke' && message.name === 'composeExtension/query'){
      message.type = 'composeExtension';
      console.log('message.value:', JSON.stringify(message.value));
      // message.value: {"commandId":"exampleCmd","parameters":[{"name":"keyword","value":"dddd"}],"queryOptions":{"skip":0,"count":25}}
      var query_params = message.value.parameters.filter(function(f){
        return f.name == 'keyword';
      });
      if(query_params.length > 0){
        message.text = query_params[0].value;
      }
    }

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next) {

    if (message.type == 'message') message.type = 'message_received';

    if (!message.conversation.isGroup && message.type == 'message_received') {
        message.type = 'direct_message';
    } else if (message.conversation.isGroup && message.type == 'message_received') {

        // start by setting this to a mention, meaning that the bot's name was _somewhere_ in the string
        message.type = 'mention';

        // check to see if this is a direct mention ,meaning bot was mentioned at start of string
        for (var e = 0; e < message.entities.length; e++) {
          var entity = message.entities[0];
          if (entity.type == 'mention' && message.text) {
            var pattern = new RegExp(message.recipient.id);
            if (entity.mentioned.id.match(pattern)) {
              var clean = new RegExp('^' + entity.text+'\\s+');

              if (message.text.match(clean)) {
                console.log('CLEANING THE DIRECT MENTION', clean);

                message.text = message.text.replace(clean,'');
                console.log('CLEANED', message.text);
                message.type = 'direct_mention';
              }
            }
          }
        }
    }

    next();
  });

  // controller.middleware.receive.use(function(bot, message, next) {
  //   console.log('RECEIVING ', message.type);
  //   next();
  //
  // });
  //
  // controller.middleware.send.use(function(bot, message, next) {
  //   console.log('SENDING ', JSON.stringify(message,null,2));
  //   next();
  // });

  controller.startTicking();
  return controller;
}

module.exports = TeamsBot;
<commit_msg>remove some consoles<commit_after>var Botkit = require(__dirname + '/CoreBot.js');
var express = require('express');
var bodyParser = require('body-parser');
var querystring = require('querystring');
var request = require('request');

function TeamsBot(configuration) {
  var controller = Botkit(configuration || {});

  var token = null;

  request(
    {
      uri: 'https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token',
      form: {
        grant_type: 'client_credentials',
        client_id: configuration.client_id,
        client_secret: configuration.client_secret,
        scope: 'https://api.botframework.com/.default'
      }
    },
    function(err, res, body) {
      var json = JSON.parse(body);
      token = json.access_token;
    }
  );

  controller.defineBot(function(botkit, config) {
    var bot = {
      type: 'teams',
      botkit: botkit,
      config: config || {},
      utterances: botkit.utterances
    };

    bot.startConversation = function(message, cb) {
      botkit.startConversation(this, message, cb);
    };

    bot.createConversation = function(message, cb) {
      botkit.createConversation(this, message, cb);
    };

    bot.openConvo = function(src, members, cb) {
        var serviceUrl = src.serviceUrl + 'v3/conversations';
        var data = {
          isGroup: true,
          bot: src.recipient,
          members: members,
          channelData: src.channelData,
          // title: 'new convo title',
        };

        request(
          {
            method: 'POST',
            json: true,
            headers: {
              'content-type': 'application/json',
              Authorization: 'Bearer ' + token
            },
            body: data,
            uri: serviceUrl
          },
          function(err, res, body) {
            if (body.error) {
              cb(body.error);
            } else {
              cb(err, body);
            }
          }
        );
    };

    bot.send = function(message, cb) {
      var serviceUrl = message.serviceUrl;

      var serviceUrl =
        serviceUrl +
        '/v3/conversations/' +
        message.conversation.id +
        '/activities';

      var data = {
        type: 'message',
        recipient: message.recipient,
        from: message.from,
      //  conversation: message.conversation,
        // textFormat: 'string',
        text: message.text,
        attachments: message.attachments,
        attachmentLayout: message.attachmentLayout || 'list',
      };

      request(
        {
          method: 'POST',
          json: true,
          headers: {
            'content-type': 'application/json',
            Authorization: 'Bearer ' + token
          },
          body: data,
          uri: serviceUrl
        },
        function(err, res, body) {

          if (err && cb) {
            return cb(er);
          }
          if (!body) {
            if (cb) { return cb('Error parsing json response'); }
          }

          if (body.error) {
            if (cb) { return cb(body.error) }
          }

          if (cb) { cb(null, body); }

        }
      );
    };

    bot.replyToComposeExtension = function(src, resp, cb){
      // console.log('src: ', src);
      src.http_res.send(resp)
      if(cb){
        cb();
      }
    }

    bot.replyInThread = function(src, resp, cb) {

      // make sure this does NOT include the activity id
      src.original_message.conversation = src.channelData.channel;

      bot.reply(src, resp, cb);

    }

    bot.reply = function(src, resp, cb) {
      if(src.type === 'composeExtension'){
        bot.replyToComposeExtension(src, resp, cb)
      }
      if (typeof resp == 'string') {
        resp = {
          text: resp
        };
      }

      resp.serviceUrl = src.original_message.serviceUrl;
      resp.from = src.original_message.recipient;
      resp.recipient = src.original_message.from;
      resp.channel = src.channel;
      resp.conversation = src.original_message.conversation;

      bot.say(resp, cb);
    };

    bot.findConversation = function(message, cb) {
      botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
      for (var t = 0; t < botkit.tasks.length; t++) {
        for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
          if (
            botkit.tasks[t].convos[c].isActive() &&
            botkit.tasks[t].convos[c].source_message.user == message.user
          ) {
            botkit.debug('FOUND EXISTING CONVO!');
            cb(botkit.tasks[t].convos[c]);
            return;
          }
        }
      }

      cb();
    };
    return bot;
  });

  controller.setupWebserver = function(port, cb) {
    if (!port) {
      throw new Error('Cannot start webserver without a port');
    }
    if (isNaN(port)) {
      throw new Error('Specified port is not a valid number');
    }

    var static_dir = process.cwd() + '/public';

    if (
      controller.config &&
      controller.config.webserver &&
      controller.config.webserver.static_dir
    )
      static_dir = controller.config.webserver.static_dir;

    controller.config.port = port;

    controller.webserver = express();
    controller.webserver.use(bodyParser.json());
    controller.webserver.use(bodyParser.urlencoded({ extended: true }));
    controller.webserver.use(express.static(static_dir));

    var server = controller.webserver.listen(
      controller.config.port,
      controller.config.hostname,
      function() {
        controller.log(
          '** Starting webserver on port ' + controller.config.port
        );
        if (cb) {
          cb(null, controller.webserver);
        }
      }
    );

    return controller;
  };

  controller.configureIncomingWebhook = function() {
    controller.webserver.post('/teams/receive', function(req, res) {

      var bot = controller.spawn({});
      var message = req.body;
      bot.serviceUrl = message.serviceUrl;

      controller.ingest(bot, message, res);

    });
  };

  controller.middleware.ingest.use(function(bot, message, res, next) {

      res.status(200);
      if (message.name != 'composeExtension/query') {
        // send a result back immediately
        res.send('');
      }

      message.http_res = res;
      next();

  });

  controller.middleware.normalize.use(function(bot, message, next) {

    message.user = message.from.id;
    message.channel = message.conversation.id;

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next){

    if (message.type === 'invoke' && message.name === 'composeExtension/query'){
      message.type = 'composeExtension';
      var query_params = message.value.parameters.filter(function(f){
        return f.name == 'keyword';
      });
      if(query_params.length > 0){
        message.text = query_params[0].value;
      }
    }

    next();

  });

  controller.middleware.categorize.use(function(bot, message, next) {

    if (message.type == 'message') message.type = 'message_received';

    if (!message.conversation.isGroup && message.type == 'message_received') {
        message.type = 'direct_message';
    } else if (message.conversation.isGroup && message.type == 'message_received') {

        // start by setting this to a mention, meaning that the bot's name was _somewhere_ in the string
        message.type = 'mention';

        // check to see if this is a direct mention ,meaning bot was mentioned at start of string
        for (var e = 0; e < message.entities.length; e++) {
          var entity = message.entities[0];
          if (entity.type == 'mention' && message.text) {
            var pattern = new RegExp(message.recipient.id);
            if (entity.mentioned.id.match(pattern)) {
              var clean = new RegExp('^' + entity.text+'\\s+');

              if (message.text.match(clean)) {

                message.text = message.text.replace(clean,'');
                message.type = 'direct_mention';
              }
            }
          }
        }
    }

    next();
  });

  // controller.middleware.receive.use(function(bot, message, next) {
  //   console.log('RECEIVING ', message.type);
  //   next();
  //
  // });
  //
  // controller.middleware.send.use(function(bot, message, next) {
  //   console.log('SENDING ', JSON.stringify(message,null,2));
  //   next();
  // });

  controller.startTicking();
  return controller;
}

module.exports = TeamsBot;
<|endoftext|>