<commit_before>'use strict';

var babelRegister = require('babel-register');
var babelPresetEs2015 = require('babel-preset-es2015');
var fs = require('fs');
var gulp = require('gulp');
var karmaConfig = require('karma/lib/config');
var karmaFirefoxLauncher = require('karma-firefox-launcher');
var karmaIevms = require('karma-ievms');
var karmaSafariLauncher = require('karma-safari-launcher');
var karmaSauceLauncher = require('karma-sauce-launcher');
var mocha = require('gulp-mocha');
var normalizeOptions = require('../options');
var openFile = require('open');
var path = require('path');
var runSequence = require('run-sequence');

module.exports = function(options) {
	options = normalizeOptions(options);
	var taskPrefix = options.taskPrefix;

	gulp.task(taskPrefix + 'test', function(done) {
		runSequence(taskPrefix + 'test:unit', function() {
			done();
		});
	});

	gulp.task(taskPrefix + 'test:unit', [taskPrefix + 'soy'], function(done) {
		runKarma(options, {singleRun: true}, function() {
			done();
		});
	});

	gulp.task(taskPrefix + 'test:coverage', [taskPrefix + 'soy'], function(done) {
		var configFile = 'metal-karma-config/';
		configFile += options.noSoy ? 'no-soy-coverage' : 'coverage';
		runKarma(
			options,
			{
				configFile: require.resolve(configFile),
				singleRun: true
			},
			function() {
				done();
			},
			'coverage'
		);
	});

	gulp.task(taskPrefix + 'test:coverage:open', [taskPrefix + 'test:coverage'], function(done) {
		openFile(path.resolve('coverage/lcov/lcov-report/index.html'));
		done();
	});

	gulp.task(taskPrefix + 'test:browsers', [taskPrefix + 'soy'], function(done) {
		runKarma(
			options,
			{
				browsers: options.testBrowsers,
				plugins: [karmaFirefoxLauncher, karmaSafariLauncher, karmaIevms],
				singleRun: true
			},
			done,
			'browsers'
		);
	});

	gulp.task(taskPrefix + 'test:saucelabs', [taskPrefix + 'soy'], function(done) {
		var launchers = options.testSaucelabsBrowsers;
		var config = {
			browsers: Object.keys(launchers),

			browserDisconnectTimeout: 10000,
			browserDisconnectTolerance: 2,
			browserNoActivityTimeout: 240000,

			captureTimeout: 240000,
			customLaunchers: launchers,

			plugins: [karmaSauceLauncher],

			reporters: ['progress', 'saucelabs'],

			sauceLabs: {
				testName: 'MetalJS tests',
				recordScreenshots: false,
				startConnect: true,
				connectOptions: {
					port: 5757,
					logfile: 'sauce_connect.log'
				}
			},

			singleRun: true
		};
		if (process.env.TRAVIS_JOB_NUMBER) {
			config.sauceLabs.startConnect = false;
			config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
		}
		runKarma(options, config, done, 'sauce');
	});

	gulp.task(taskPrefix + 'test:watch', [taskPrefix + 'soy'], function(done) {
		gulp.watch(options.soySrc, [taskPrefix + 'soy']);

		runKarma(options, {}, done);
	});

	gulp.task('test:node', function() {
		return gulp.src(options.testNodeSrc)
			.pipe(mocha({
				compilers: [babelRegister({
					presets: [babelPresetEs2015],
					sourceMaps: 'both'
				})]
			}));
	});
};

// Private helpers
// ===============

function runKarma(options, config, done, opt_suffix) {
	var suffix = opt_suffix ? '-' + opt_suffix : '';
	var configFile = path.resolve('karma' + suffix + '.conf.js');
	var configGenericFile = path.resolve('karma.conf.js');
	if (fs.existsSync(configFile)) {
		config.configFile = configFile;
	} else if (fs.existsSync(configGenericFile)) {
		config.configFile = configGenericFile;
	} else if (!config.configFile) {
		configFile = 'metal-karma-config';
		configFile += options.noSoy ? '/no-soy' : '';
		config.configFile = require.resolve(configFile);
	}

	if (!config.basePath) {
		config.basePath = process.cwd();
	}

	if (config.plugins) {
		var configObj = new karmaConfig.Config();
		require(config.configFile)(configObj);

		var plugins = config.plugins;
		delete config.plugins;

		configObj.set(config);
		configObj.plugins = (configObj.plugins || []).concat(plugins);
		config = configObj;
	}
	new options.karma.Server(config, done).start();
}
<commit_msg>Fixes error when running tasks on Windows<commit_after>'use strict';

var babelRegister = require('babel-register');
var babelPresetEs2015 = require('babel-preset-es2015');
var fs = require('fs');
var gulp = require('gulp');
var karmaConfig = require('karma/lib/config');
var karmaFirefoxLauncher = require('karma-firefox-launcher');
var karmaSafariLauncher = require('karma-safari-launcher');
var karmaSauceLauncher = require('karma-sauce-launcher');
var mocha = require('gulp-mocha');
var normalizeOptions = require('../options');
var openFile = require('open');
var path = require('path');
var runSequence = require('run-sequence');

module.exports = function(options) {
	options = normalizeOptions(options);
	var taskPrefix = options.taskPrefix;

	gulp.task(taskPrefix + 'test', function(done) {
		runSequence(taskPrefix + 'test:unit', function() {
			done();
		});
	});

	gulp.task(taskPrefix + 'test:unit', [taskPrefix + 'soy'], function(done) {
		runKarma(options, {singleRun: true}, function() {
			done();
		});
	});

	gulp.task(taskPrefix + 'test:coverage', [taskPrefix + 'soy'], function(done) {
		var configFile = 'metal-karma-config/';
		configFile += options.noSoy ? 'no-soy-coverage' : 'coverage';
		runKarma(
			options,
			{
				configFile: require.resolve(configFile),
				singleRun: true
			},
			function() {
				done();
			},
			'coverage'
		);
	});

	gulp.task(taskPrefix + 'test:coverage:open', [taskPrefix + 'test:coverage'], function(done) {
		openFile(path.resolve('coverage/lcov/lcov-report/index.html'));
		done();
	});

	gulp.task(taskPrefix + 'test:browsers', [taskPrefix + 'soy'], function(done) {
		var plugins = [karmaFirefoxLauncher, karmaSafariLauncher];
		if (process.platform !== 'win32') {
			// Windows breaks when requiring "karma-ievms", and we don't really need
			// it there, so only require it when not on windows.
			plugins.push(require('karma-ievms'));
		}
		runKarma(
			options,
			{
				browsers: options.testBrowsers,
				plugins: plugins,
				singleRun: true
			},
			done,
			'browsers'
		);
	});

	gulp.task(taskPrefix + 'test:saucelabs', [taskPrefix + 'soy'], function(done) {
		var launchers = options.testSaucelabsBrowsers;
		var config = {
			browsers: Object.keys(launchers),

			browserDisconnectTimeout: 10000,
			browserDisconnectTolerance: 2,
			browserNoActivityTimeout: 240000,

			captureTimeout: 240000,
			customLaunchers: launchers,

			plugins: [karmaSauceLauncher],

			reporters: ['progress', 'saucelabs'],

			sauceLabs: {
				testName: 'MetalJS tests',
				recordScreenshots: false,
				startConnect: true,
				connectOptions: {
					port: 5757,
					logfile: 'sauce_connect.log'
				}
			},

			singleRun: true
		};
		if (process.env.TRAVIS_JOB_NUMBER) {
			config.sauceLabs.startConnect = false;
			config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
		}
		runKarma(options, config, done, 'sauce');
	});

	gulp.task(taskPrefix + 'test:watch', [taskPrefix + 'soy'], function(done) {
		gulp.watch(options.soySrc, [taskPrefix + 'soy']);

		runKarma(options, {}, done);
	});

	gulp.task('test:node', function() {
		return gulp.src(options.testNodeSrc)
			.pipe(mocha({
				compilers: [babelRegister({
					presets: [babelPresetEs2015],
					sourceMaps: 'both'
				})]
			}));
	});
};

// Private helpers
// ===============

function runKarma(options, config, done, opt_suffix) {
	var suffix = opt_suffix ? '-' + opt_suffix : '';
	var configFile = path.resolve('karma' + suffix + '.conf.js');
	var configGenericFile = path.resolve('karma.conf.js');
	if (fs.existsSync(configFile)) {
		config.configFile = configFile;
	} else if (fs.existsSync(configGenericFile)) {
		config.configFile = configGenericFile;
	} else if (!config.configFile) {
		configFile = 'metal-karma-config';
		configFile += options.noSoy ? '/no-soy' : '';
		config.configFile = require.resolve(configFile);
	}

	if (!config.basePath) {
		config.basePath = process.cwd();
	}

	if (config.plugins) {
		var configObj = new karmaConfig.Config();
		require(config.configFile)(configObj);

		var plugins = config.plugins;
		delete config.plugins;

		configObj.set(config);
		configObj.plugins = (configObj.plugins || []).concat(plugins);
		config = configObj;
	}
	new options.karma.Server(config, done).start();
}
<|endoftext|>


<commit_before>var assert = require("assert");

function maybe_call(f, that, args) {
    // call a function if it is defined
    if (typeof f !== "undefined" && f !== null) {
        f.apply(that, args);
    }
}

function opt_or_default(opt_value, default_value) {
    // return opt_value if defined, else default
    return ((typeof opt_value !== 'undefined')
            ? opt_value : default_value);
}

function assert_opt_exists(domain, opts, key) {
    // raise error if opt not defined
    var opt = opts[key];
    assert.ok((typeof opt !== "undefined"),
              domain + " requires option '" + key + "'.");
    return opt;
}

function ImTester(api, opts) {
    // a utility for testing state machine transitions
    // optional opts:
    //   - custom_setup: setup(api)
    //   - custom_teardown: teardown(api, saved_user)
    //   - max_response_length: maximum length of responses
    //                          (default: 163, use null to disable check)
    //   - async: default value for whether tests should enable the
    //            asynchronous DummyApi and return promises (default: false)
    var self = this;

    opts = opts || {};
    self.custom_setup = opts.custom_setup;
    self.custom_teardown = opts.custom_teardown;
    self.max_response_length = opt_or_default(opts.max_response_length,
                                             163);
    self.async = opt_or_default(opts.async, false);

    self.api = api;

    self.refresh_api = function () {
        self.api.reset();
        self.api.reset_im();
        return self.api;
    };

    self.format_logs = function(msg) {
        var logs = "\nLogs:\n\n" + self.api.logs.join("\n");
        return msg ? (msg + "\n" + logs) : logs;
    };

    self.assert_ok = function(value, msg) {
        assert.ok(value, self.format_logs(msg));
    };

    self.assert_equal = function(actual, expected, msg) {
        msg = msg ? msg + " " : "";
        msg += ("Expected: " + JSON.stringify(expected) +
                " but got: " + JSON.stringify(actual));
        assert.equal(actual, expected, self.format_logs(msg));
    };

    self.assert_deep_equal = function(actual, expected, msg) {
        msg = msg ? msg + " " : "";
        msg += ("Expected: " + JSON.stringify(expected) +
                " but got: " + JSON.stringify(actual));
        assert.deepEqual(actual, expected, self.format_logs(msg));
    };

    self.check_state = function (opts) {
        // required opts:
        //   - user: current user dictionary
        //   - next_state: expected state after processing message
        //   - content: content to send from user
        //   - response: expected response to user
        // optional opts:
        //   - setup: setup(api)
        //   - teardown: teardown(api, saved_user)
        //   - max_response_length: maximum allowed response length
        //                          (default: self.max_response_length,
        //                           use null to disable check)
        //   - session_event: session event from user (default: "continue")
        //   - continue_session: whether response asks for session to continue
        //                       (default: true)
        //   - from_addr: user's address (default: "1234567")
        //   - message_id: sandbox message id (default: "123")
        //   - async: enable asynchronous dummy api calls and return
        //            a promise that is called once this test completes
        //            (default: self.async)

        opts = opts || {};

        var user = assert_opt_exists("check_state", opts, "user");
        var next_state = assert_opt_exists("check_state", opts, "next_state");
        var content = assert_opt_exists("check_state", opts, "content");
        var expected_response = assert_opt_exists("check_state", opts, "response");

        var max_response_length = opt_or_default(opts.max_response_length,
                                                self.max_response_length);
        var session_event = opt_or_default(opts.session_event, "continue");
        var continue_session = opt_or_default(opts.continue_session, true);
        var from_addr = opt_or_default(opts.from_addr, "1234567");
        var helper_metadata = opt_or_default(opts.helper_metadata, {});
        var message_id = opt_or_default(opts.message_id, "123");
        var async = opt_or_default(opts.async, self.async);

        // setup api
        var api = self.refresh_api();
        api.async = async;
        var user_key = "users." + from_addr;
        api.kv_store[user_key] = user;

        maybe_call(self.custom_setup, self, [api]);
        maybe_call(opts.setup, self, [api]);

        api.add_reply({
            cmd: "outbound.reply_to"
        });

        // send message
        api.on_inbound_message({
            cmd: "inbound-message",
            msg: {
                from_addr: from_addr,
                content: content,
                message_id: message_id,
                session_event: session_event,
                helper_metadata: helper_metadata
            }
        });

        // check result
        var check_result = function() {
            var saved_user = api.kv_store[user_key];
            self.assert_equal(saved_user.current_state, next_state,
                              "Unexpected next_state.");
            var reply = api.request_calls.shift();
            var response = reply.content;

            self.assert_ok(response, "Reply content empty.");
            self.assert_ok(response.match(expected_response),
                           "Reply '" + response + "' did not match '" +
                           expected_response + "'");
            if (max_response_length !== null) {
                self.assert_ok(response.length <= max_response_length,
                               "Reply '" + response + "' (length: " +
                               response.length + ") is longer than " +
                               max_response_length + " characters");
            }

            self.assert_equal(reply.continue_session, continue_session,
                              "Unexpected continue_session.");
            self.assert_deep_equal(api.request_calls, [],
                                   "Outstanding request calls found.");
            self.assert_equal(api.done_calls, 1,
                              "The api.done() method was not called" +
                              " exactly once.");

            maybe_call(self.custom_teardown, self, [api, saved_user]);
            maybe_call(opts.teardown, self, [api, saved_user]);
        };

        if (async) {
            var p = api.pending_calls_complete();
            return p.then(check_result);
        }
        else {
            check_result();
        }
    };

    self.check_close = function(opts) {
        // required opts:
        //   - user: user dictionary
        //   - next_state: expected next state
        // optional opts:
        //   - setup: setup(api) (default: do nothing)
        //   - teardown: teardown(api, saved_user) (default: do nothing)
        //   - content: message content (default: "User Timeout")
        //   - session_event: session event (default: "close")
        //   - from_addr: user's address (default: "1234567")
        //   - message_id: message id (default: "123")
        //   - async: enable asynchronous dummy api calls and return
        //            a promise that is called once this test completes
        //            (default: self.async)

        opts = opts || {};

        var user = assert_opt_exists("check_close", opts, "user");
        var next_state = assert_opt_exists("check_close", opts, "next_state");

        var content = opt_or_default(opts.content, "User Timeout");
        var session_event = opt_or_default(opts.session_event, "close");
        var from_addr = opt_or_default(opts.from_addr, "1234567");
        var message_id = opt_or_default(opts.message_id, "123");
        var async = opt_or_default(opts.async, self.async);

        // setup api
        var api = self.refresh_api();
        api.async = async;
        var user_key = "users." + from_addr;
        api.kv_store[user_key] = user;

        maybe_call(self.custom_setup, self, [api]);
        maybe_call(opts.setup, self, [api]);

        // send message
        api.on_inbound_message({
            cmd: "inbound-message",
            msg: {
                from_addr: from_addr,
                session_event: session_event,
                content: content,
                message_id: message_id
            }
        });

        // check result
        var check_result = function() {
            var saved_user = api.kv_store[user_key];
            self.assert_equal(saved_user.current_state, next_state,
                              "Unexpected next_state.");
            self.assert_deep_equal(api.request_calls, [],
                                   "Outstanding request calls found.");
            self.assert_equal(api.done_calls, 1,
                              "The api.done() method was not called" +
                              " exactly once.");

            maybe_call(self.custom_teardown, self, [api, saved_user]);
            maybe_call(opts.teardown, self, [api, saved_user]);
        };

        if (async) {
            var p = api.pending_calls_complete();
            return p.then(check_result);
        }
        else {
            check_result();
        }
    };
}

function DummyIm() {
    var self = this;

    self.user = {};
    self.user.answers = {};
    self.user.current_state = null;

    self.set_user_state = function(state_name) {
        self.user.current_state = state_name;
    };

    self.set_user_answer = function(state_name, answer) {
        self.user.answers[state_name] = answer;
    };
}

// exports

this.ImTester = ImTester;
this.DummyIm = DummyIm;
<commit_msg>Update test utils to work with the state event updates<commit_after>var assert = require("assert");

var events = require("./events");
var states = require("./states");
var state_machine = require("./state_machine");

var Eventable = events.Eventable;
var InteractionMachine = state_machine.InteractionMachine;

function maybe_call(f, that, args) {
    // call a function if it is defined
    if (typeof f !== "undefined" && f !== null) {
        f.apply(that, args);
    }
}

function opt_or_default(opt_value, default_value) {
    // return opt_value if defined, else default
    return ((typeof opt_value !== 'undefined')
            ? opt_value : default_value);
}

function assert_opt_exists(domain, opts, key) {
    // raise error if opt not defined
    var opt = opts[key];
    assert.ok((typeof opt !== "undefined"),
              domain + " requires option '" + key + "'.");
    return opt;
}

function ImTester(api, opts) {
    // a utility for testing state machine transitions
    // optional opts:
    //   - custom_setup: setup(api)
    //   - custom_teardown: teardown(api, saved_user)
    //   - max_response_length: maximum length of responses
    //                          (default: 163, use null to disable check)
    //   - async: default value for whether tests should enable the
    //            asynchronous DummyApi and return promises (default: false)
    var self = this;

    opts = opts || {};
    self.custom_setup = opts.custom_setup;
    self.custom_teardown = opts.custom_teardown;
    self.max_response_length = opt_or_default(opts.max_response_length,
                                             163);
    self.async = opt_or_default(opts.async, false);

    self.api = api;

    self.refresh_api = function () {
        self.api.reset();
        self.api.reset_im();
        return self.api;
    };

    self.format_logs = function(msg) {
        var logs = "\nLogs:\n\n" + self.api.logs.join("\n");
        return msg ? (msg + "\n" + logs) : logs;
    };

    self.assert_ok = function(value, msg) {
        assert.ok(value, self.format_logs(msg));
    };

    self.assert_equal = function(actual, expected, msg) {
        msg = msg ? msg + " " : "";
        msg += ("Expected: " + JSON.stringify(expected) +
                " but got: " + JSON.stringify(actual));
        assert.equal(actual, expected, self.format_logs(msg));
    };

    self.assert_deep_equal = function(actual, expected, msg) {
        msg = msg ? msg + " " : "";
        msg += ("Expected: " + JSON.stringify(expected) +
                " but got: " + JSON.stringify(actual));
        assert.deepEqual(actual, expected, self.format_logs(msg));
    };

    self.check_state = function (opts) {
        // required opts:
        //   - user: current user dictionary
        //   - next_state: expected state after processing message
        //   - content: content to send from user
        //   - response: expected response to user
        // optional opts:
        //   - setup: setup(api)
        //   - teardown: teardown(api, saved_user)
        //   - max_response_length: maximum allowed response length
        //                          (default: self.max_response_length,
        //                           use null to disable check)
        //   - session_event: session event from user (default: "continue")
        //   - continue_session: whether response asks for session to continue
        //                       (default: true)
        //   - from_addr: user's address (default: "1234567")
        //   - message_id: sandbox message id (default: "123")
        //   - async: enable asynchronous dummy api calls and return
        //            a promise that is called once this test completes
        //            (default: self.async)

        opts = opts || {};

        var user = assert_opt_exists("check_state", opts, "user");
        var next_state = assert_opt_exists("check_state", opts, "next_state");
        var content = assert_opt_exists("check_state", opts, "content");
        var expected_response = assert_opt_exists("check_state", opts, "response");

        var max_response_length = opt_or_default(opts.max_response_length,
                                                self.max_response_length);
        var session_event = opt_or_default(opts.session_event, "continue");
        var continue_session = opt_or_default(opts.continue_session, true);
        var from_addr = opt_or_default(opts.from_addr, "1234567");
        var helper_metadata = opt_or_default(opts.helper_metadata, {});
        var message_id = opt_or_default(opts.message_id, "123");
        var async = opt_or_default(opts.async, self.async);

        // setup api
        var api = self.refresh_api();
        api.async = async;
        var user_key = "users." + from_addr;
        api.kv_store[user_key] = user;

        maybe_call(self.custom_setup, self, [api]);
        maybe_call(opts.setup, self, [api]);

        api.add_reply({
            cmd: "outbound.reply_to"
        });

        // send message
        api.on_inbound_message({
            cmd: "inbound-message",
            msg: {
                from_addr: from_addr,
                content: content,
                message_id: message_id,
                session_event: session_event,
                helper_metadata: helper_metadata
            }
        });

        // check result
        var check_result = function() {
            var saved_user = api.kv_store[user_key];
            self.assert_equal(saved_user.current_state, next_state,
                              "Unexpected next_state.");
            var reply = api.request_calls.shift();
            var response = reply.content;

            self.assert_ok(response, "Reply content empty.");
            self.assert_ok(response.match(expected_response),
                           "Reply '" + response + "' did not match '" +
                           expected_response + "'");
            if (max_response_length !== null) {
                self.assert_ok(response.length <= max_response_length,
                               "Reply '" + response + "' (length: " +
                               response.length + ") is longer than " +
                               max_response_length + " characters");
            }

            self.assert_equal(reply.continue_session, continue_session,
                              "Unexpected continue_session.");
            self.assert_deep_equal(api.request_calls, [],
                                   "Outstanding request calls found.");
            self.assert_equal(api.done_calls, 1,
                              "The api.done() method was not called" +
                              " exactly once.");

            maybe_call(self.custom_teardown, self, [api, saved_user]);
            maybe_call(opts.teardown, self, [api, saved_user]);
        };

        if (async) {
            var p = api.pending_calls_complete();
            return p.then(check_result);
        }
        else {
            check_result();
        }
    };

    self.check_close = function(opts) {
        // required opts:
        //   - user: user dictionary
        //   - next_state: expected next state
        // optional opts:
        //   - setup: setup(api) (default: do nothing)
        //   - teardown: teardown(api, saved_user) (default: do nothing)
        //   - content: message content (default: "User Timeout")
        //   - session_event: session event (default: "close")
        //   - from_addr: user's address (default: "1234567")
        //   - message_id: message id (default: "123")
        //   - async: enable asynchronous dummy api calls and return
        //            a promise that is called once this test completes
        //            (default: self.async)

        opts = opts || {};

        var user = assert_opt_exists("check_close", opts, "user");
        var next_state = assert_opt_exists("check_close", opts, "next_state");

        var content = opt_or_default(opts.content, "User Timeout");
        var session_event = opt_or_default(opts.session_event, "close");
        var from_addr = opt_or_default(opts.from_addr, "1234567");
        var message_id = opt_or_default(opts.message_id, "123");
        var async = opt_or_default(opts.async, self.async);

        // setup api
        var api = self.refresh_api();
        api.async = async;
        var user_key = "users." + from_addr;
        api.kv_store[user_key] = user;

        maybe_call(self.custom_setup, self, [api]);
        maybe_call(opts.setup, self, [api]);

        // send message
        api.on_inbound_message({
            cmd: "inbound-message",
            msg: {
                from_addr: from_addr,
                session_event: session_event,
                content: content,
                message_id: message_id
            }
        });

        // check result
        var check_result = function() {
            var saved_user = api.kv_store[user_key];
            self.assert_equal(saved_user.current_state, next_state,
                              "Unexpected next_state.");
            self.assert_deep_equal(api.request_calls, [],
                                   "Outstanding request calls found.");
            self.assert_equal(api.done_calls, 1,
                              "The api.done() method was not called" +
                              " exactly once.");

            maybe_call(self.custom_teardown, self, [api, saved_user]);
            maybe_call(opts.teardown, self, [api, saved_user]);
        };

        if (async) {
            var p = api.pending_calls_complete();
            return p.then(check_result);
        }
        else {
            check_result();
        }
    };
}

function DummyIm() {
    var self = this;
    Eventable.call(self);

    self.user = {};
    self.user.answers = {};
    self.user.current_state = null;

    self.set_user_state = function(state_name) {
        self.user.current_state = state_name;
    };

    self.set_user_answer = function(state_name, answer) {
        self.user.answers[state_name] = answer;
    };

    self.emit.session_new = function() {
    };
}

this.ImTester = ImTester;
this.DummyIm = DummyIm;
<|endoftext|>


<commit_before>var ItemsVue = new Vue({
    el: '#heroes',
    data: {
        Items: []
    },
    mounted: function () {
        var self = this;
        $.ajax({
            url: "https://api-loc-rafaeel16.c9users.io/api/heroes",
            method: 'GET',
            success: function (data) {
                self.Items = data;
            },
            error: function (error) {
                alert(JSON.stringify(error));
            }
        });
    }
});<commit_msg>add ajax positions<commit_after>var Heroes = new Vue({
    el: '#heroes',
    data: {
        Items: []
    },
    mounted: function () {
        var self = this;
        $.ajax({
            url: "https://api-loc-rafaeel16.c9users.io/api/heroes",
            method: 'GET',
            success: function (data) {
                self.Items = data;
            },
            error: function (error) {
                alert(JSON.stringify(error));
            }
        });
    }
});

var Positions = new Vue({
    el: '#positions',
    data: {
        Items: []
    },
    mounted: function () {
        var self = this;
        $.ajax({
            url: "https://api-loc-rafaeel16.c9users.io/api/positions",
            method: 'GET',
            success: function (data) {
                self.Items = data;
            },
            error: function (error) {
                alert(JSON.stringify(error));
            }
        });
    }
});<|endoftext|>


<commit_before>
/**
 * Dependencies
 */

var d3 = require('d3');
var debug = require('debug')('transitive');
var Display = require('./display');
var Emitter = require('emitter');
var Graph = require('./graph');
var Pattern = require('./pattern');
var Route = require('./route');
var Stop = require('./stop');
var Journey = require('./journey');
var Styler = require('./styler');
var OtpProfile = require('./profile');

var toFunction = require('to-function');
var each = require('each');

/**
 * Expose `Transitive`
 */

module.exports = Transitive;

/**
 * Expose `d3`
 */

module.exports.d3 = Transitive.prototype.d3 = d3;

/**
 * Expose `version`
 */

module.exports.version = '0.0.0';

/**
 * Create a new instance of `Transitive`
 *
 * @param {Element} element to render to
 * @param {Object} data to render
 * @param {Object} styles to apply
 * @param {Object} options object
 */

function Transitive(el, data, styles, options) {
  if (!(this instanceof Transitive)) {
    return new Transitive(el, data, styles, options);
  }

  this.clearFilters();
  this.data = data;
  this.setElement(el);
  this.style = new Styler(styles);
}

/**
 * Mixin `Emitter`
 */

Emitter(Transitive.prototype);

/**
 * Add a data filter
 *
 * @param {String} type
 * @param {String|Object|Function} filter, gets passed to `to-function`
 */

Transitive.prototype.addFilter =
Transitive.prototype.filter = function(type, filter) {
  if (!this._filter[type]) this._filter[type] = [];
  this._filter[type].push(toFunction(filter));

  return this;
};

/**
 * Clear all data filters
 *
 * @param {String} filter type
 */

Transitive.prototype.clearFilters = function(type) {
  if (type) {
    this._filter[type] = [];
  } else {
    this._filter = {
      patterns: [],
      routes: [],
      stops: []
    };
  }

  this.emit('clear filters', this);
  return this;
};

/**
 * Load
 *
 * @param {Object} data
 */

Transitive.prototype.load = function(data) {
  debug('load', data);

  this.graph = new Graph();

  // Generate the stop objects
  this.stops = {};
  applyFilters(data.stops, this._filter.stops).forEach(function (data) {
    this.stops[data.stop_id] = new Stop(data);
  }, this);

  // A list of stops that will become vertices in the network graph. This
  // includes all stops that serve as a pattern endpoint and/or a
  // convergence/divergence point between patterns
  var vertexStops = {};

  // object maps stop ids to arrays of unique stop_ids reachable from that stop
  var adjacentStops = {};

  // Generate the routes & patterns
  this.routes = {};
  this.patterns = {};
  this.journeys = {};

  if(data.routes) {
    applyFilters(data.routes, this._filter.routes).forEach(function (routeData) {
      var route = this.routes[routeData.route_id] = new Route(routeData);
      // iterate through the Route's constituent Patterns
      applyFilters(routeData.patterns, this._filter.patterns).forEach(function (patternData, i) {
        var pattern = this.processStopSequence(patternData, patternData.pattern_id, vertexStops, adjacentStops);
        pattern.route = route;
      }, this);
    }, this);
  }

  if(data.journeys) {
    data.journeys.forEach((function(journeyData) {
      var journey = new Journey(journeyData);
      var pattern = this.processStopSequence(journeyData, journeyData.journey_id, vertexStops, adjacentStops);
      journey.addPattern(pattern);
      //journey.combinedPattern = pattern;
      this.journeys[journeyData.journey_id] = journey;

    }).bind(this));
  }

  // determine the convergence/divergence vertex stops by looking for stops w/ >2 adjacent stops
  for (var stopId in adjacentStops) {
    if (adjacentStops[stopId].length > 2) {
      vertexStops[stopId] = this.stops[stopId];
      this.stops[stopId].isBranchPoint = true;
    }
  }

  // populate the vertices in the graph object
  for (stopId in vertexStops) {
    var stop = vertexStops[stopId];
    var vertex = this.graph.addVertex(stop, 0, 0);
    stop.graphVertex = vertex;
  }

  populateGraphEdges(this.patterns, this.graph);

  //this.graph.convertTo1D();
  this.graph.apply2DOffsets();
  //this.placeStopLabels();
  this.setScale();

  this.emit('load', this);
  return this;
};

/**
 *
 */

Transitive.prototype.processStopSequence = function(data, id, vertexStops, adjacentStops) {
  // Create the Pattern object
  var pattern = this.patterns[id] = new Pattern(id, data);

  // iterate through this pattern's stops, associating stops/patterns with
  // each other and initializing the adjacentStops table
  var previousStop = null;
  data.stops.forEach(function (stopInfo) {
    var stop = this.stops[stopInfo.stop_id];

    pattern.stops.push(stop);
    stop.patterns.push(pattern);

    // called for each pair of adjacent stops in pattern
    if (previousStop) {
      addStopAdjacency(adjacentStops, stop.getId(), previousStop.getId());
      addStopAdjacency(adjacentStops, previousStop.getId(), stop.getId());
    }

    previousStop = stop;

    if(stopInfo.transfer) {
      vertexStops[stop.getId()] = stop;
      stop.isJourneyTransfer = true;
    }
  }, this);

  // add the start and end stops to the vertexStops collection
  var firstStop = pattern.stops[0];
  if(!(firstStop.getId() in vertexStops)) {
    vertexStops[firstStop.getId()] = firstStop;
  }
  firstStop.isEndPoint = true;

  var lastStop = pattern.stops[pattern.stops.length-1];
  if(!(lastStop.getId() in vertexStops)) {
    vertexStops[lastStop.getId()] = lastStop;
  }
  lastStop.isEndPoint = true;

  return pattern;
};

/**
 * LoadProfile
 *
 */

Transitive.prototype.loadProfile = function(profileData, endpoint, callback, maxOptions) {
  var profile = new OtpProfile(profileData, endpoint, (function(data) {
    this.data = data;
    callback.call(this, data);
  }).bind(this), maxOptions);

  // this.style.load(profile.styles);

  // apply computed behaviors
  this.on('render', function (transitive) {
    each(profile.computed, function (behavior) {
      behavior(transitive);
    });
  });

};

/**
 * Render
 */

Transitive.prototype.render = function() {
  this.load(this.data);

  var display = this.display;
  var offsetLeft = this.el.offsetLeft;
  var offsetTop = this.el.offsetTop;

  // remove all old svg elements
  this.display.empty();

  // initialize the pattern svg elements
  for (var key in this.patterns) {
    var pattern = this.patterns[key];
    pattern.refreshRenderData();
    pattern.draw(this.display, 10);
  }

  // Draw the stop svg elements
  for (key in this.stops) this.stops[key].draw(this.display);

  this.refresh();

  this.emit('render', this);
  return this;
};

/**
 * Render to
 *
 * @param {Element} el
 */

Transitive.prototype.renderTo = function(el) {
  this.setElement(el);
  this.render();

  this.emit('render to', this);
  return this;
};

/**
 * Refresh
 */

Transitive.prototype.refresh = function() {

  for (var key in this.patterns) {
    this.patterns[key].clearOffsets();
  }

  this.graph.apply2DOffsets();

  // clear the stop render data
  for (key in this.stops) this.stops[key].renderData = [];

  // refresh the patterns
  for (key in this.patterns) {
    var pattern = this.patterns[key];
    pattern.refreshRenderData(); // also updates the stop-level renderData

    for(var i = 0; i < pattern.segments.length; i++) {
      this.style.renderPattern(this.display, pattern.segments[i].lineGraph);
    }

    pattern.refresh(this.display, this.style);
  }

  // refresh the stops
  for (key in this.stops) {
    var stop = this.stops[key];
    if (!stop.svgGroup) continue; // check if this stop is not currently rendered

    this.style.renderStop(this.display, stop);
    stop.refresh(this.display);
  }

  this.emit('refresh', this);
  return this;
};

/**
 * Set element
 */

Transitive.prototype.setElement = function(el) {
  if (this.el) this.el.innerHTML = null;

  this.el = el;

  this.display = new Display(el);
  this.display.zoom.on('zoom', this.refresh.bind(this));

  this.setScale();

  this.emit('set element', this);
  return this;
};

/**
 * Set scale
 */

Transitive.prototype.setScale = function() {
  if (this.display && this.el && this.graph) {
    this.display.setScale(this.el.clientHeight, this.el.clientWidth,
      this.graph);
  }

  this.emit('set scale', this);
  return this;
};

/**
 * Apply an array of filters to an array of data
 *
 * @param {Array} data
 * @param {Array} filters
 */

function applyFilters(data, filters) {
  filters.forEach(function (filter) {
    data = data.filter(filter);
  });

  return data;
}

/**
 * Helper function for stopAjacency table
 *
 * @param {Stop} adjacent stops list
 * @param {Stop} stopA
 * @param {Stop} stopB
 */

function addStopAdjacency(stops, stopIdA, stopIdB) {
  if (!stops[stopIdA]) stops[stopIdA] = [];
  if (stops[stopIdA].indexOf(stopIdB) === -1) stops[stopIdA].push(stopIdB);
}

/**
 * Populate the graph edges
 *
 * @param {Object} patterns
 * @param {Graph} graph
 */

function populateGraphEdges(patterns, graph) {
  // vertex associated with the last vertex stop we passed in this sequence
  var lastVertex = null;

  // collection of 'internal' (i.e. non-vertex) stops passed
  // since the last vertex stop
  var internalStops = [];

  for (var id in patterns) {
    var pattern = patterns[id];

    lastVertex = null;
    var lastVertexIndex = 0;

    for(var i=0; i< pattern.stops.length; i++) {
      var stop = pattern.stops[i];
      if (stop.graphVertex) { // this is a vertex stop
        if (lastVertex !== null) {
          var edge = graph.getEquivalentEdge(internalStops, lastVertex,
            stop.graphVertex);

          if (!edge) {
            edge = graph.addEdge(internalStops, lastVertex, stop.graphVertex);
          }

          pattern.addEdge(edge);
          edge.addPattern(pattern);

          // add the segment
          var patternSegment = {
            pattern: pattern
          };

          if(pattern.journey.stops[lastVertexIndex].boardSegment) {
            patternSegment.otpSegment = pattern.journey.stops[lastVertexIndex].boardSegment;
          }
          pattern.segments.push(patternSegment);
        }

        lastVertex = stop.graphVertex;
        lastVertexIndex = i;
        internalStops = [];
      } else { // this is an internal stop
        internalStops.push(stop);
      }
    }
  }
}
<commit_msg>disable cap extension<commit_after>
/**
 * Dependencies
 */

var d3 = require('d3');
var debug = require('debug')('transitive');
var Display = require('./display');
var Emitter = require('emitter');
var Graph = require('./graph');
var Pattern = require('./pattern');
var Route = require('./route');
var Stop = require('./stop');
var Journey = require('./journey');
var Styler = require('./styler');
var OtpProfile = require('./profile');

var toFunction = require('to-function');
var each = require('each');

/**
 * Expose `Transitive`
 */

module.exports = Transitive;

/**
 * Expose `d3`
 */

module.exports.d3 = Transitive.prototype.d3 = d3;

/**
 * Expose `version`
 */

module.exports.version = '0.0.0';

/**
 * Create a new instance of `Transitive`
 *
 * @param {Element} element to render to
 * @param {Object} data to render
 * @param {Object} styles to apply
 * @param {Object} options object
 */

function Transitive(el, data, styles, options) {
  if (!(this instanceof Transitive)) {
    return new Transitive(el, data, styles, options);
  }

  this.clearFilters();
  this.data = data;
  this.setElement(el);
  this.style = new Styler(styles);
}

/**
 * Mixin `Emitter`
 */

Emitter(Transitive.prototype);

/**
 * Add a data filter
 *
 * @param {String} type
 * @param {String|Object|Function} filter, gets passed to `to-function`
 */

Transitive.prototype.addFilter =
Transitive.prototype.filter = function(type, filter) {
  if (!this._filter[type]) this._filter[type] = [];
  this._filter[type].push(toFunction(filter));

  return this;
};

/**
 * Clear all data filters
 *
 * @param {String} filter type
 */

Transitive.prototype.clearFilters = function(type) {
  if (type) {
    this._filter[type] = [];
  } else {
    this._filter = {
      patterns: [],
      routes: [],
      stops: []
    };
  }

  this.emit('clear filters', this);
  return this;
};

/**
 * Load
 *
 * @param {Object} data
 */

Transitive.prototype.load = function(data) {
  debug('load', data);

  this.graph = new Graph();

  // Generate the stop objects
  this.stops = {};
  applyFilters(data.stops, this._filter.stops).forEach(function (data) {
    this.stops[data.stop_id] = new Stop(data);
  }, this);

  // A list of stops that will become vertices in the network graph. This
  // includes all stops that serve as a pattern endpoint and/or a
  // convergence/divergence point between patterns
  var vertexStops = {};

  // object maps stop ids to arrays of unique stop_ids reachable from that stop
  var adjacentStops = {};

  // Generate the routes & patterns
  this.routes = {};
  this.patterns = {};
  this.journeys = {};

  if(data.routes) {
    applyFilters(data.routes, this._filter.routes).forEach(function (routeData) {
      var route = this.routes[routeData.route_id] = new Route(routeData);
      // iterate through the Route's constituent Patterns
      applyFilters(routeData.patterns, this._filter.patterns).forEach(function (patternData, i) {
        var pattern = this.processStopSequence(patternData, patternData.pattern_id, vertexStops, adjacentStops);
        pattern.route = route;
      }, this);
    }, this);
  }

  if(data.journeys) {
    data.journeys.forEach((function(journeyData) {
      var journey = new Journey(journeyData);
      var pattern = this.processStopSequence(journeyData, journeyData.journey_id, vertexStops, adjacentStops);
      journey.addPattern(pattern);
      //journey.combinedPattern = pattern;
      this.journeys[journeyData.journey_id] = journey;

    }).bind(this));
  }

  // determine the convergence/divergence vertex stops by looking for stops w/ >2 adjacent stops
  for (var stopId in adjacentStops) {
    if (adjacentStops[stopId].length > 2) {
      vertexStops[stopId] = this.stops[stopId];
      this.stops[stopId].isBranchPoint = true;
    }
  }

  // populate the vertices in the graph object
  for (stopId in vertexStops) {
    var stop = vertexStops[stopId];
    var vertex = this.graph.addVertex(stop, 0, 0);
    stop.graphVertex = vertex;
  }

  populateGraphEdges(this.patterns, this.graph);

  //this.graph.convertTo1D();
  this.graph.apply2DOffsets();
  //this.placeStopLabels();
  this.setScale();

  this.emit('load', this);
  return this;
};

/**
 *
 */

Transitive.prototype.processStopSequence = function(data, id, vertexStops, adjacentStops) {
  // Create the Pattern object
  var pattern = this.patterns[id] = new Pattern(id, data);

  // iterate through this pattern's stops, associating stops/patterns with
  // each other and initializing the adjacentStops table
  var previousStop = null;
  data.stops.forEach(function (stopInfo) {
    var stop = this.stops[stopInfo.stop_id];

    pattern.stops.push(stop);
    stop.patterns.push(pattern);

    // called for each pair of adjacent stops in pattern
    if (previousStop) {
      addStopAdjacency(adjacentStops, stop.getId(), previousStop.getId());
      addStopAdjacency(adjacentStops, previousStop.getId(), stop.getId());
    }

    previousStop = stop;

    if(stopInfo.transfer) {
      vertexStops[stop.getId()] = stop;
      stop.isJourneyTransfer = true;
    }
  }, this);

  // add the start and end stops to the vertexStops collection
  var firstStop = pattern.stops[0];
  if(!(firstStop.getId() in vertexStops)) {
    vertexStops[firstStop.getId()] = firstStop;
  }
  firstStop.isEndPoint = true;

  var lastStop = pattern.stops[pattern.stops.length-1];
  if(!(lastStop.getId() in vertexStops)) {
    vertexStops[lastStop.getId()] = lastStop;
  }
  lastStop.isEndPoint = true;

  return pattern;
};

/**
 * LoadProfile
 *
 */

Transitive.prototype.loadProfile = function(profileData, endpoint, callback, maxOptions) {
  var profile = new OtpProfile(profileData, endpoint, (function(data) {
    this.data = data;
    callback.call(this, data);
  }).bind(this), maxOptions);

  // this.style.load(profile.styles);

  // apply computed behaviors
  this.on('render', function (transitive) {
    each(profile.computed, function (behavior) {
      behavior(transitive);
    });
  });

};

/**
 * Render
 */

Transitive.prototype.render = function() {
  this.load(this.data);

  var display = this.display;
  var offsetLeft = this.el.offsetLeft;
  var offsetTop = this.el.offsetTop;

  // remove all old svg elements
  this.display.empty();

  // initialize the pattern svg elements
  for (var key in this.patterns) {
    var pattern = this.patterns[key];
    pattern.refreshRenderData();
    pattern.draw(this.display, 0); // 10);
  }

  // Draw the stop svg elements
  for (key in this.stops) this.stops[key].draw(this.display);

  this.refresh();

  this.emit('render', this);
  return this;
};

/**
 * Render to
 *
 * @param {Element} el
 */

Transitive.prototype.renderTo = function(el) {
  this.setElement(el);
  this.render();

  this.emit('render to', this);
  return this;
};

/**
 * Refresh
 */

Transitive.prototype.refresh = function() {

  for (var key in this.patterns) {
    this.patterns[key].clearOffsets();
  }

  this.graph.apply2DOffsets();

  // clear the stop render data
  for (key in this.stops) this.stops[key].renderData = [];

  // refresh the patterns
  for (key in this.patterns) {
    var pattern = this.patterns[key];
    pattern.refreshRenderData(); // also updates the stop-level renderData

    for(var i = 0; i < pattern.segments.length; i++) {
      this.style.renderPattern(this.display, pattern.segments[i].lineGraph);
    }

    pattern.refresh(this.display, this.style);
  }

  // refresh the stops
  for (key in this.stops) {
    var stop = this.stops[key];
    if (!stop.svgGroup) continue; // check if this stop is not currently rendered

    this.style.renderStop(this.display, stop);
    stop.refresh(this.display);
  }

  this.emit('refresh', this);
  return this;
};

/**
 * Set element
 */

Transitive.prototype.setElement = function(el) {
  if (this.el) this.el.innerHTML = null;

  this.el = el;

  this.display = new Display(el);
  this.display.zoom.on('zoom', this.refresh.bind(this));

  this.setScale();

  this.emit('set element', this);
  return this;
};

/**
 * Set scale
 */

Transitive.prototype.setScale = function() {
  if (this.display && this.el && this.graph) {
    this.display.setScale(this.el.clientHeight, this.el.clientWidth,
      this.graph);
  }

  this.emit('set scale', this);
  return this;
};

/**
 * Apply an array of filters to an array of data
 *
 * @param {Array} data
 * @param {Array} filters
 */

function applyFilters(data, filters) {
  filters.forEach(function (filter) {
    data = data.filter(filter);
  });

  return data;
}

/**
 * Helper function for stopAjacency table
 *
 * @param {Stop} adjacent stops list
 * @param {Stop} stopA
 * @param {Stop} stopB
 */

function addStopAdjacency(stops, stopIdA, stopIdB) {
  if (!stops[stopIdA]) stops[stopIdA] = [];
  if (stops[stopIdA].indexOf(stopIdB) === -1) stops[stopIdA].push(stopIdB);
}

/**
 * Populate the graph edges
 *
 * @param {Object} patterns
 * @param {Graph} graph
 */

function populateGraphEdges(patterns, graph) {
  // vertex associated with the last vertex stop we passed in this sequence
  var lastVertex = null;

  // collection of 'internal' (i.e. non-vertex) stops passed
  // since the last vertex stop
  var internalStops = [];

  for (var id in patterns) {
    var pattern = patterns[id];

    lastVertex = null;
    var lastVertexIndex = 0;

    for(var i=0; i< pattern.stops.length; i++) {
      var stop = pattern.stops[i];
      if (stop.graphVertex) { // this is a vertex stop
        if (lastVertex !== null) {
          var edge = graph.getEquivalentEdge(internalStops, lastVertex,
            stop.graphVertex);

          if (!edge) {
            edge = graph.addEdge(internalStops, lastVertex, stop.graphVertex);
          }

          pattern.addEdge(edge);
          edge.addPattern(pattern);

          // add the segment
          var patternSegment = {
            pattern: pattern
          };

          if(pattern.journey.stops[lastVertexIndex].boardSegment) {
            patternSegment.otpSegment = pattern.journey.stops[lastVertexIndex].boardSegment;
          }
          pattern.segments.push(patternSegment);
        }

        lastVertex = stop.graphVertex;
        lastVertexIndex = i;
        internalStops = [];
      } else { // this is an internal stop
        internalStops.push(stop);
      }
    }
  }
}
<|endoftext|>


<commit_before>const fs = require('fs');
const Twitter = require('twitter');
const schedule = require('node-schedule');
const config = require('../config.js');
const helpers = require('./helpers.js');

/**
 * Twitterbot - Handles all communication between Ariadne and the Twitter
 * Streaming and REST APIs
 *
 * @module Twitterbot
 */

let client = {};
let stream;
let restartInterval = 1000 * 30; // Default to 30 seconds
const tweetHandlers = [];
const tweetMax = 140; // Max characters in a tweet

/**
 * Sends a Tweet using the configured Twitter account
 * @param  {string}   status   The message to be sent
 * @param  {object}   [params]   The parameters sent to the Twitter REST API
 * @param  {Function} callback Code to be run upon completion. Receives error
 * object as argument upon failure to send.
 */
function tweet(status, params, callback) {
  if (null == params) {
    // Handle if no params or callback given
    params = {};
    callback = (err) => {
      if (err) console.log(err);
    };
  } else if ('function' === typeof params && 'undefined' === typeof callback) {
    // Handle in case no params are provided
    callback = params;
    params = {};
  }

  // Check for message
  if (status && 'string' === typeof status && '' !== status) {
    const nextTweet = [];

    // Check if @reply and add screen name to front of message
    if (params.in_reply_to_status_id
      && params.in_reply_to_screen_name
      && false === status.includes(`@${params.in_reply_to_screen_name}`)) {
      status = `@${params.in_reply_to_screen_name}\n${status}`; // eslint-disable-line no-param-reassign
    }

    // Check if multiple tweets needed and add numbering to the first
    if (-1 === status.search(/^\d+\)/) && tweetMax < status.length) {
      status = `1) ${status}`; // eslint-disable-line no-param-reassign
    }

    // Split message in case of multiple tweets
    if (tweetMax < status.length) {
      console.log('splitting message into tweets...');
      const splitIndex = helpers.CALC_SPLIT(status, tweetMax);

      // Add numbering to next tweet
      nextTweet.push(`${Number.parseInt(status.slice(status.search(/\d+\)/), status.indexOf(')')), 10) + 1})`);

      // Add screen name to front of message
      nextTweet.push(`${params.in_reply_to_status_id && params.in_reply_to_screen_name ? `@${params.in_reply_to_screen_name} ` : ''}`);

      // Add rest of message to tweet
      nextTweet.push(`\n${status.slice(splitIndex).trim()}`);

      // Trim to current tweet
      status = status.slice(0, splitIndex); // eslint-disable-line no-param-reassign
    }

    // Send tweet
    console.log(`tweeting: ${status}`);
    client.post('statuses/update', Object.assign({}, params, { status }), (err, data, res) => {
      if (err) {
        console.log(`tweet failed: response body: ${res.body}`);
        callback(new Error(`Twitter error: ${err}`));
      } else {
        console.log(`tweet success: ${data.id_str}`);
        // Send next tweet if first succeeds
        if (0 !== nextTweet.length) {
          // Set @reply params, if empty
          if (false === {}.hasOwnProperty.call(params, 'in_reply_to_status_id')) {
            params.in_reply_to_status_id = data.id_str;
          }

          setTimeout(() => {
            tweet(nextTweet.join(' '), params, (err) => {
              if (err) {
                console.log('Tweet series message failed');
              }
            });
          }, 3000);
        }
        callback(null);
      }
    });
  } else if (callback) {
    callback(new Error('No message provided')); // Write Tests
  } else {
    throw new Error('Failure with no callback');
  }
}

/**
 * Attach a command handler to the Twitterbot, allowing you to create your own
 * functionality.
 * @param  {string}   command     The keyword that triggers the twitterbot's response
 * @param  {string}   [description] Explanation of the commands functionality.
 * @param  {string}   [usage]       Example of proper usage w/arguments
 * @param  {Function} callback    Defines what operations are performed when triggered.
 */
function attach(command, description, usage, callback) {
  /* eslint-disable no-param-reassign */
  if ('function' === typeof description) {
    callback = description;
    description = 'No description provided';
    usage = `${command} <args>`;
  }
  if ('function' === typeof usage) {
    callback = usage;
    usage = `${command} <args>`;
  }
  /* eslint-enable no-param-reassign */

  if ('undefined' === typeof command || 'function' === typeof command || '' === command || 1 < command.split(' ').length) {
    throw new Error('No command to watch');
  }
  if ('undefined' === typeof callback) {
    throw new Error('No callback to return');
  }

  tweetHandlers.push({ command, description, usage, callback });
  console.log(`module loaded: '${command}'`);
}

// Checks if a specified command exist or returns a list of all valid commands.
function commands() {
  return tweetHandlers;
}

/**
 * Destorys the Twitter Stream connection, waits and then restores it.
 * @param  {string} screenName Screen name that the new stream will follow
 */
function restartStream(screenName) {
  if (1000 * 60 * 20 >= restartInterval) {
    restartInterval *= 2;
  }
  console.log(`deactivated twitter stream...restarting in ${restartInterval}`);
  stream.destroy();
  setTimeout(() => {
    activateStream(screenName);
  }, restartInterval);
}

/**
 * Creates a new connection to Twitters Stream API
 * @param  {string} screenName Screen name to track in the stream
 */
function activateStream(screenName) {
  console.log('activating twitter stream...');
  stream = client.stream('statuses/filter', { track: screenName });
  stream.on('data', (event) => {
    if (helpers.IS_TWEET(event)) {
      const command = event.text.split(' ')[1];
      tweetHandlers.forEach((handler) => {
        if (handler.command.toLowerCase() === command.toLowerCase()) {
          console.log(`command (${command}) triggered...`);
          handler.callback(event.text.split(' ').slice(2).join(' '), (err, res) => {
            if (err || 'undefined' === typeof res) {
              console.log(`${handler.command} middleware failed: ${err}`);
            } else {
              const params = {
                in_reply_to_status_id: event.id_str,
                in_reply_to_screen_name: event.user.screen_name,
              };
              tweet(res, params, (err) => {
                if (err) {
                  console.log(err);
                }
              });
            }
          });
        }
      });
    } else {
      console.log('twitter message:');
      console.log(event);
    }
  });

  // Handle stream errors
  stream.on('error', (err) => {
    if ('Status Code: 420' === err.message) {
      console.log('rate limit hit');
      restartStream(screenName);
    } else {
      console.log(`streaming error: ${err.message}`);
    }
  });
}

/**
 * Check the Twitter API and fetch the screen_name attached to the supplied credentials
 * @param  {object}   client   Current instance of Node.js Twitter client
 * @param  {int}   interval Time to wait if connection hits rate limit (in ms)
 * @param  {Function} callback
 */
function fetchUsername(client, interval, callback) {
  client.get('account/settings', (err, data, res) => {
    console.log('fetching twitter account info...');

    // Log non-breaking errors
    if (err) {
      console.log(err);
    }

    // Restart if connection limit hit
    if (420 === res.statusCode) {
      console.log(`connection limit exceeded...retrying in ${interval / 1000} seconds`);
      setInterval(() => {
        fetchUsername(client, interval * 2, () => {
          console.log('twitter connection established...');
        });
      }, interval);
    }

    // Check for screen name
    if (data && data.screen_name) {
      callback(null, data.screen_name);
    } else {
      callback(Error('no screen name.'));
    }
  });
}

/**
 * Configure and start the twitterbot
 * @param  {Function} callback
 */
function init(callback) {
  if (callback) {
    console.log('checking twitterbot configuration...');
    if (!config.twitter.consumer_key || !config.twitter.consumer_secret
      || !config.twitter.access_token_key || !config.twitter.access_token_secret) {
      callback(Error('no twitter api credentials.'));
    } else {
      client = new Twitter(config.twitter);
      // Fetch Username
      fetchUsername(client, restartInterval, (err, screenName) => {
        if (err) {
          callback(Error('no screen name'));
        } else {
          console.log(`twitterbot screen name: @${screenName}`);
          // Watch Twitter Feed
          activateStream(screenName);
          callback(null);
        }
      });
    }
  } else {
    throw new Error('No Callback Provided');
  }
}

function randomQuote() {
  fs.readFile('quotes.txt', 'utf8', (err, data) => {
    if (err) throw err;
    const lines = data.split('\n');
    const rand = Math.floor(Math.random() * lines.length);

    const quote = lines[rand];
    const newString = [];
    const number = quote.match(/\d+$/gm);
    const statement = quote.match(/".+"/g);
    newString.push(`${number}. ${statement[0].slice(1, -1)}`);

    tweet(newString.join(''));
  });
}

// TODO: Write Tests
module.exports.init = init;
module.exports.tweet = tweet;
module.exports.attach = attach;
module.exports.commands = commands;
module.exports.randomQuote = randomQuote;

// Reset the rate limit timer backoff every two hours
schedule.scheduleJob('0 */2 * * *', () => {
  restartInterval = 1000 * 30;
});<commit_msg>Undo my fuckery<commit_after>const fs = require('fs');
const Twitter = require('twitter');
const schedule = require('node-schedule');
const config = require('../config.js');
const helpers = require('./helpers.js');

/**
 * Twitterbot - Handles all communication between Ariadne and the Twitter
 * Streaming and REST APIs
 *
 * @module Twitterbot
 */

let client = {};
let stream;
let restartInterval = 1000 * 30; // Default to 30 seconds
const tweetInterval = 1000 * 2;
const tweetHandlers = [];
const tweetMax = 140; // Max characters in a tweet

/**
 * Sends a Tweet using the configured Twitter account
 * @param  {string}   status   The message to be sent
 * @param  {object}   [params]   The parameters sent to the Twitter REST API
 * @param  {Function} callback Code to be run upon completion. Receives error
 * object as argument upon failure to send.
 */
function tweet(status, params, callback) {
  if ('undefined' === typeof params) {
    // Handle if no params or callback given
    params = {};
    callback = (err) => {
      if (err) console.log(err);
    };
  } else if ('function' === typeof params && 'undefined' === typeof callback) {
    // Handle in case no params are provided
    callback = params;
    params = {};
  }

  // Check for message
  if (status && 'string' === typeof status && '' !== status) {
    const nextTweet = [];

    // Check if @reply and add screen name to front of message
    if (params.in_reply_to_status_id
      && params.in_reply_to_screen_name
      && false === status.includes(`@${params.in_reply_to_screen_name}`)) {
      status = `@${params.in_reply_to_screen_name}\n${status}`; // eslint-disable-line no-param-reassign
    }

    // Check if multiple tweets needed and add numbering to the first
    if (-1 === status.search(/^\d+\)/) && tweetMax < status.length) {
      status = `1) ${status}`; // eslint-disable-line no-param-reassign
    }

    // Split message in case of multiple tweets
    if (tweetMax < status.length) {
      console.log('splitting message into tweets...');
      const splitIndex = helpers.CALC_SPLIT(status, tweetMax);

      // Add numbering to next tweet
      nextTweet.push(`${Number.parseInt(status.slice(status.search(/\d+\)/), status.indexOf(')')), 10) + 1})`);

      // Add screen name to front of message
      nextTweet.push(`${params.in_reply_to_status_id && params.in_reply_to_screen_name ? `@${params.in_reply_to_screen_name} ` : ''}`);

      // Add rest of message to tweet
      nextTweet.push(`\n${status.slice(splitIndex).trim()}`);

      // Trim to current tweet
      status = (status.slice(0, splitIndex)); // eslint-disable-line no-param-reassign
    }

    // Send tweet
    console.log(`tweeting: ${status}`);
    client.post('statuses/update', Object.assign({}, params, { status }), (err, data, res) => {
      if (err) {
        console.log(`tweet failed: response body: ${res.body}`);
        callback(new Error(`Twitter error: ${err}`));
      } else {
        console.log(`tweet success: ${data.id_str}`);
        // Send next tweet if first succeeds
        if (0 !== nextTweet.length) {
          setTimeout(() => {
            tweet(nextTweet.join(' '), params, (err) => {
              if (err) {
                console.log('Tweet series message failed');
              }
            });
          }, tweetInterval);
        }
        callback(null);
      }
    });
  } else if (callback) {
    callback(new Error('No message provided')); // Write Tests
  } else {
    throw new Error('Failure with no callback');
  }
}

/**
 * Attach a command handler to the Twitterbot, allowing you to create your own
 * functionality.
 * @param  {string}   command     The keyword that triggers the twitterbot's response
 * @param  {string}   [description] Explanation of the commands functionality.
 * @param  {string}   [usage]       Example of proper usage w/arguments
 * @param  {Function} callback    Defines what operations are performed when triggered.
 */
function attach(command, description, usage, callback) {
  /* eslint-disable no-param-reassign */
  if ('function' === typeof description) {
    callback = description;
    description = 'No description provided';
    usage = `${command} <args>`;
  }
  if ('function' === typeof usage) {
    callback = usage;
    usage = `${command} <args>`;
  }
  /* eslint-enable no-param-reassign */

  if ('undefined' === typeof command || 'function' === typeof command || '' === command || 1 < command.split(' ').length) {
    throw new Error('No command to watch');
  }
  if ('undefined' === typeof callback) {
    throw new Error('No callback to return');
  }

  tweetHandlers.push({ command, description, usage, callback });
  console.log(`module loaded: '${command}'`);
}

// Checks if a specified command exist or returns a list of all valid commands.
function commands() {
  return tweetHandlers;
}

/**
 * Destorys the Twitter Stream connection, waits and then restores it.
 * @param  {string} screenName Screen name that the new stream will follow
 */
function restartStream(screenName) {
  if (1000 * 60 * 20 >= restartInterval) {
    restartInterval *= 2;
  }
  console.log(`deactivated twitter stream...restarting in ${restartInterval}`);
  stream.destroy();
  setTimeout(() => {
    activateStream(screenName);
  }, restartInterval);
}

/**
 * Creates a new connection to Twitters Stream API
 * @param  {string} screenName Screen name to track in the stream
 */
function activateStream(screenName) {
  console.log('activating twitter stream...');
  stream = client.stream('statuses/filter', { track: screenName });
  stream.on('data', (event) => {
    if (helpers.IS_TWEET(event)) {
      const command = event.text.split(' ')[1];
      tweetHandlers.forEach((handler) => {
        if (handler.command.toLowerCase() === command.toLowerCase()) {
          console.log(`command (${command}) triggered...`);
          handler.callback(event.text.split(' ').slice(2).join(' '), (err, res) => {
            if (err || 'undefined' === typeof res) {
              console.log(`${handler.command} middleware failed: ${err}`);
            } else {
              const params = {
                in_reply_to_status_id: event.id_str,
                in_reply_to_screen_name: event.user.screen_name,
              };
              tweet(res, params, (err) => {
                if (err) {
                  console.log(err);
                }
              });
            }
          });
        }
      });
    } else {
      console.log('twitter message:');
      console.log(event);
    }
  });

  // Handle stream errors
  stream.on('error', (err) => {
    if ('Status Code: 420' === err.message) {
      console.log('rate limit hit');
      restartStream(screenName);
    } else {
      console.log(`streaming error: ${err.message}`);
    }
  });
}

/**
 * Check the Twitter API and fetch the screen_name attached to the supplied credentials
 * @param  {object}   client   Current instance of Node.js Twitter client
 * @param  {int}   interval Time to wait if connection hits rate limit (in ms)
 * @param  {Function} callback
 */
function fetchUsername(client, interval, callback) {
  client.get('account/settings', (err, data, res) => {
    console.log('fetching twitter account info...');

    // Log non-breaking errors
    if (err) {
      console.log(err);
    }

    // Restart if connection limit hit
    if (420 === res.statusCode) {
      console.log(`connection limit exceeded...retrying in ${interval / 1000} seconds`);
      setInterval(() => {
        fetchUsername(client, interval * 2, () => {
          console.log('twitter connection established...');
        });
      }, interval);
    }

    // Check for screen name
    if (data && data.screen_name) {
      callback(null, data.screen_name);
    } else {
      callback(Error('no screen name.'));
    }
  });
}

/**
 * Configure and start the twitterbot
 * @param  {Function} callback
 */
function init(callback) {
  if (callback) {
    console.log('checking twitterbot configuration...');
    if (!config.twitter.consumer_key || !config.twitter.consumer_secret
      || !config.twitter.access_token_key || !config.twitter.access_token_secret) {
      callback(Error('no twitter api credentials.'));
    } else {
      client = new Twitter(config.twitter);
      // Fetch Username
      fetchUsername(client, restartInterval, (err, screenName) => {
        if (err) {
          callback(Error('no screen name'));
        } else {
          console.log(`twitterbot screen name: @${screenName}`);
          // Watch Twitter Feed
          activateStream(screenName);
          callback(null);
        }
      });
    }
  } else {
    throw new Error('No Callback Provided');
  }
}

function randomQuote() {
  fs.readFile('quotes.txt', 'utf8', (err, data) => {
    if (err) throw err;
    const lines = data.split('\n');
    const rand = Math.floor(Math.random() * lines.length);

    const quote = lines[rand];
    const newString = [];
    const number = quote.match(/\d+$/gm);
    const statement = quote.match(/".+"/g);
    newString.push(`${number}. ${statement[0].slice(1, -1)}`);

    tweet(newString.join(''));
  });
}

// TODO: Write Tests
module.exports.init = init;
module.exports.tweet = tweet;
module.exports.attach = attach;
module.exports.commands = commands;
module.exports.randomQuote = randomQuote;

// Reset the rate limit timer backoff every two hours
schedule.scheduleJob('0 */2 * * *', () => {
  restartInterval = 1000 * 30;
});<|endoftext|>


<commit_before>//Because Browserify encapsulates every module, use strict won't apply to the global scope and break everything
'use strict';

/**
 * Element constructor
 *
 * @class Element
 * @classdesc A single UI element
 *
 * @param {Vector2} position - The position of this element
 */
var Element = function(position) {

	/**
	 * @property {Vector2} position - The position of this element
	 */
	this.position = position;

	/**
	 * @property {Boolean} visible - Is this UI element visible or not
	 */
	this.visible = true;

	/**
	 * @property {Number} alpha - The opacity of the element
	 */
	this.alpha = 1;

	/**
	 * @property {Number} scale - The scale factor of the element
	 */
	this.scale = 1;

};

Element.prototype = {

	/**
	 * Toggle the visibility of this UI element
	 * @protected
	 */
	toggleVisibility: function() {

		//Invert the visibility of the UI element
		this.visible = !this.visible;

	}

};

//Export the Browserify module
module.exports = Element;
<commit_msg>An element now holds a width property, height property and a hover property.<commit_after>//Because Browserify encapsulates every module, use strict won't apply to the global scope and break everything
'use strict';

/**
 * Element constructor
 *
 * @class Element
 * @classdesc A single UI element
 *
 * @param {Vector2} position - The position of this element
 * @param {Number} width - The width of this element
 * @param {Number} height - The height of this element
 */
var Element = function(position, width, height) {

	/**
	 * @property {Vector2} position - The position of this element
	 */
	this.position = position;

	/**
	 * @property {Number} width - The width of this element
	 */
	this.width = width || 300;

	/**
	 * @property {Number} height - The height of this element
	 */
	this.height = height || 300;

	/**
	 * @property {Boolean} visible - Is this UI element visible or not
	 */
	this.visible = true;

	/**
	 * @property {Number} alpha - The opacity of the element
	 */
	this.alpha = 1;

	/**
	 * @property {Number} scale - The scale factor of the element
	 */
	this.scale = 1;

	/**
	 * @property {Boolean} hover - Is the user currently hovering this element
	 */
	this.hover = false;

};

Element.prototype = {

	/**
	 * Toggle the visibility of this UI element
	 * @protected
	 */
	toggleVisibility: function() {

		//Invert the visibility of the UI element
		this.visible = !this.visible;

	}

};

//Export the Browserify module
module.exports = Element;
<|endoftext|>


<commit_before>import {
	useState, useEffect, useMemo
} from 'haunted';
import { match } from './match';

export const
	documentUrl = () => window.location.href.replace(window.location.origin, ''),

	createElement = (tag, props = {}) => {
		if (!customElements.get(tag)) {
			throw new Error(`Element ${tag} is not defined`);
		}
		return Object.assign(document.createElement(tag), props);
	},

	useUrl = () => {
		const [url, setUrl] = useState(documentUrl);
		useEffect(() => {
			const onPopState = () => setUrl(documentUrl);
			window.addEventListener('popstate', onPopState);
			return () => window.removeEventListener('popstate', onPopState);
		}, [setUrl]);

		return url;
	},

	useRoutes = routes => {
		const url = useUrl(),
			matched = useMemo(() => match(routes, url), [routes, url]);
		return useMemo(() => {
			if (!matched) {
				return;
			}
			const {
				handle,
				...route
			} = matched;
			return {
				...route,
				handle: (...args) => handle(route, ...args)
			};
		}, [matched]);
	},

	navigate = (url, state = null, {
		notify = true, replace = true
	} = {}) => {
		(replace ? history.replaceState : history.pushState).call(history, state, '', url);
		if (notify) {
			queueMicrotask(() =>
				window.dispatchEvent(new CustomEvent('popstate', {
					bubbles: false
				})));
		}
	};

<commit_msg>fix(create-element): document.createElement breaks useContext<commit_after>import {
	useState, useEffect, useMemo, html
} from 'haunted';
import { match } from './match';

export const
	documentUrl = () => window.location.href.replace(window.location.origin, ''),

	createElement = (tag, props = {}) => {
		if (!customElements.get(tag)) {
			throw new Error(`Element ${tag} is not defined`);
		}

		// NOTE: previously this code used document.createElement, but it breaks some
		// expectations that lit-haunted makes: the fresh element is not attached to the DOM,
		// causing the useContext registration mechanism to fail

		// using a lit TemplateResult makes sure the element is connected when created
		// lit-html does not have support for dynamic tags, but we can create a TemplateResult
		// by calling the template tag function with the appropriate parameters
		// `html(strings[], ...values)`
		if (Object.keys(props).length > 0) {
			const strings = Object.keys(props).map(prop => ` .${prop}=`);
			return html(
				[`<${tag}${strings[0]}`, ...strings.slice(1), '>'],
				...Object.values(props)
			);
		}

		return html([`<${tag}>`]);
	},

	useUrl = () => {
		const [url, setUrl] = useState(documentUrl);
		useEffect(() => {
			const onPopState = () => setUrl(documentUrl);
			window.addEventListener('popstate', onPopState);
			return () => window.removeEventListener('popstate', onPopState);
		}, [setUrl]);

		return url;
	},

	useRoutes = routes => {
		const url = useUrl(),
			matched = useMemo(() => match(routes, url), [routes, url]);
		return useMemo(() => {
			if (!matched) {
				return;
			}
			const {
				handle,
				...route
			} = matched;
			return {
				...route,
				handle: (...args) => handle(route, ...args)
			};
		}, [matched]);
	},

	navigate = (url, state = null, {
		notify = true, replace = true
	} = {}) => {
		(replace ? history.replaceState : history.pushState).call(history, state, '', url);
		if (notify) {
			queueMicrotask(() =>
				window.dispatchEvent(new CustomEvent('popstate', {
					bubbles: false
				})));
		}
	};

<|endoftext|>


<commit_before>//       telegram.link
//
//       Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//       Released under the BSD-3-Clause license
//       http://telegram.link

// Import dependencies
var BigInteger = require('jsbn');

// Export
exports.createMessageId = createMessageId;
exports.runSeries = runSeries;
exports.retrieveArgumentNames = retrieveArgumentNames;
exports.retryUntilItsDone = retryUntilItsDone;
exports.Logger = require('./logger');

// Run an `Array of tasks` (you can mix synchronous and asynchronous tasks) in `series`, calling back when done or
// if some exceptions occur.
// - Each task must be a `Function` and all the async tasks must have the first argument named `callback`
// in order to be recognised as asynchronous task.
// - Synchronous task will be automatically executed surrounded by a try/catch statement,
// any exception will stop the flow and will be propagated to the main callback as first argument.
// - ASynchronous task must manage any exception internally and propagate it to callback as first argument.
// - The `callback` and `tasks` arguments are mandatory (`callback` can be null but be present anyway),
// the subsequent arguments (if any) will be passed to the first task.
function runSeries(callback, tasks) {
    var logger = require('./logger')('util.runSeries');
    if (!tasks || tasks.length === 0) {
        if (logger.isDebugEnabled()) {
            logger.debug('Illegal argument, you must provide a not empty task list.');
        }
        return;
    }
    // Retrieve all the main arguments, they will be passed to the first task
    var firstTaskArgs = Array.prototype.slice.call(arguments);
    // First argument, the 'exception',  will be null
    firstTaskArgs[0] = null;
    // 'runTask' function manage each task execution, both sync and async
    var runTask = function (ex) {
        // Exit on exception
        if(ex) {
            logger.error("Exception occurs: %s", ex.stack);
            callback(ex);
            return;
        }
        // Retrieve all the arguments
        var taskArgs = Array.prototype.slice.call(arguments);
        // Take the current task if any
        var currentTask = tasks[0];
        // Check if still there is a task to run like there's no tomorrow... :)
        if (currentTask) {
            // Prepare to the next
            tasks.shift();
            // Check if the task first argument name is 'callback'
            var isAsync = retrieveArgumentNames(currentTask)[0] == 'callback';
            if (logger.isDebugEnabled()) logger.debug('Run %s task \'%s\'..',
                (isAsync ? 'async' : 'sync'), currentTask.name);
            // ASynchronous Task
            if(isAsync) {
                // First argument will be the callback wrapper to run the next task
                taskArgs[0] = runTask;
                // Run the current async task
                currentTask.apply(this, taskArgs);
            }
            // Synchronous Task
            else {
                try{
                    // Discard the 'exception' first argument
                    taskArgs.shift();
                    // Run the current sync task
                    var result = currentTask.apply(this, taskArgs);
                    // Run the next task passing the current task result
                    runTask(null, result);
                } catch (exc) {
                    // Pass the exception to the task runner
                    runTask(exc);
                }
            }
        } else {
            callback.apply(this, taskArgs);
        }
    };
    // run first task
    runTask.apply(this, firstTaskArgs);
}

// Call the async task until succeeds or the retries limit has been reached (the default is 10 attempts)
// - All the arguments are mandatory (`callback` and `retriesLimit` can be null but be present anyway),
// the subsequent arguments (if any) will be passed to the task.
function retryUntilItsDone(callback, retriesLimit, task) {
    var logger = require('./logger')('util.retryUntilItsDone');
    if (!callback || !task) {
        if (logger.isDebugEnabled()) {
            logger.debug('Illegal argument, you must provide the task.');
        }
        return;
    }
    var attempts = [];
    var limit = (retriesLimit && retriesLimit > 0 ? retriesLimit : 10);
    for (var i = 0; i < limit; i++) {
        attempts.push(task);
    }
    var attemptNumber = 0;
    var mainArgs = Array.prototype.slice.call(arguments, 2);
    (function attempt(ex1, attempts) {
        var current = attempts[0];
        if (current) {
            attemptNumber++;
            if (logger.isDebugEnabled()) logger.debug('Attempt number %s..', attemptNumber);
            mainArgs[0] = function (ex2) {
                if (ex2) {
                    attempts.shift();
                    attempt(ex2, attempts);
                    return;
                }
                if (logger.isDebugEnabled()) logger.debug('Done at the attempt n.%s', attemptNumber);
                var args = arguments;
                args[0] = null;
                callback.apply(this, args);
            };
            setTimeout(function () {
                current.apply(this, mainArgs);
            }, 100);
        } else {
            logger.warn('Fail, retries limit = %s has been reached!', attemptNumber);
            callback(ex1);
        }
    })(null, attempts);
}

// Create a message ID starting from the local time
function createMessageId() {
    var time = new BigInteger(new Date().getTime().toString());
    time = time.divide(new BigInteger((1000).toString()));
    return time.shiftLeft(32).toString();
}

// Retrieve a list of argument name of the given function - thanks John! :)
function retrieveArgumentNames(func) {
    var found = /^[\s\(]*function[^(]*\(\s*([^)]*?)\s*\)/.exec(func.toString());
    return found && found[1] ? found[1].split(/,\s*/) : [];
}

<commit_msg>Add a time synchronization utility function <commit_after>//       telegram.link
//
//       Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//       Released under the BSD-3-Clause license
//       http://telegram.link

// Import dependencies
var BigInteger = require('jsbn');

// Export
exports.createMessageId = createMessageId;
exports.timeSynchronization = timeSynchronization;
exports.runSeries = runSeries;
exports.retrieveArgumentNames = retrieveArgumentNames;
exports.retryUntilItsDone = retryUntilItsDone;
exports.Logger = require('./logger');

// Run an `Array of tasks` (you can mix synchronous and asynchronous tasks) in `series`, calling back when done or
// if some exceptions occur.
// - Each task must be a `Function` and all the async tasks must have the first argument named `callback`
// in order to be recognised as asynchronous task.
// - Synchronous task will be automatically executed surrounded by a try/catch statement,
// any exception will stop the flow and will be propagated to the main callback as first argument.
// - ASynchronous task must manage any exception internally and propagate it to callback as first argument.
// - The `callback` and `tasks` arguments are mandatory (`callback` can be null but be present anyway),
// the subsequent arguments (if any) will be passed to the first task.
function runSeries(callback, tasks) {
    var logger = require('./logger')('util.runSeries');
    if (!tasks || tasks.length === 0) {
        if (logger.isDebugEnabled()) {
            logger.debug('Illegal argument, you must provide a not empty task list.');
        }
        return;
    }
    // Retrieve all the main arguments, they will be passed to the first task
    var firstTaskArgs = Array.prototype.slice.call(arguments);
    // First argument, the 'exception',  will be null
    firstTaskArgs[0] = null;
    // 'runTask' function manage each task execution, both sync and async
    var runTask = function (ex) {
        // Exit on exception
        if (ex) {
            logger.error("Exception occurs: %s", ex.stack);
            callback(ex);
            return;
        }
        // Retrieve all the arguments
        var taskArgs = Array.prototype.slice.call(arguments);
        // Take the current task if any
        var currentTask = tasks[0];
        // Check if still there is a task to run like there's no tomorrow... :)
        if (currentTask) {
            // Prepare to the next
            tasks.shift();
            // Check if the task first argument name is 'callback'
            var isAsync = retrieveArgumentNames(currentTask)[0] == 'callback';
            if (logger.isDebugEnabled()) logger.debug('Run %s task \'%s\'..',
                (isAsync ? 'async' : 'sync'), currentTask.name);
            // ASynchronous Task
            if (isAsync) {
                // First argument will be the callback wrapper to run the next task
                taskArgs[0] = runTask;
                // Run the current async task
                currentTask.apply(this, taskArgs);
            }
            // Synchronous Task
            else {
                try {
                    // Discard the 'exception' first argument
                    taskArgs.shift();
                    // Run the current sync task
                    var result = currentTask.apply(this, taskArgs);
                    // Run the next task passing the current task result
                    runTask(null, result);
                } catch (exc) {
                    // Pass the exception to the task runner
                    runTask(exc);
                }
            }
        } else {
            callback.apply(this, taskArgs);
        }
    };
    // run first task
    runTask.apply(this, firstTaskArgs);
}

// Call the async task until succeeds or the retries limit has been reached (the default is 10 attempts)
// - All the arguments are mandatory (`callback` and `retriesLimit` can be null but be present anyway),
// the subsequent arguments (if any) will be passed to the task.
function retryUntilItsDone(callback, retriesLimit, task) {
    var logger = require('./logger')('util.retryUntilItsDone');
    if (!callback || !task) {
        if (logger.isDebugEnabled()) {
            logger.debug('Illegal argument, you must provide the task.');
        }
        return;
    }
    var attempts = [];
    var limit = (retriesLimit && retriesLimit > 0 ? retriesLimit : 10);
    for (var i = 0; i < limit; i++) {
        attempts.push(task);
    }
    var attemptNumber = 0;
    var mainArgs = Array.prototype.slice.call(arguments, 2);
    (function attempt(ex1, attempts) {
        var current = attempts[0];
        if (current) {
            attemptNumber++;
            if (logger.isDebugEnabled()) logger.debug('Attempt number %s..', attemptNumber);
            mainArgs[0] = function (ex2) {
                if (ex2) {
                    attempts.shift();
                    attempt(ex2, attempts);
                    return;
                }
                if (logger.isDebugEnabled()) logger.debug('Done at the attempt n.%s', attemptNumber);
                var args = arguments;
                args[0] = null;
                callback.apply(this, args);
            };
            setTimeout(function () {
                current.apply(this, mainArgs);
            }, 100);
        } else {
            logger.warn('Fail, retries limit = %s has been reached!', attemptNumber);
            callback(ex1);
        }
    })(null, attempts);
}

// Retrieve a list of argument name of the given function - thanks John! :)
function retrieveArgumentNames(func) {
    var found = /^[\s\(]*function[^(]*\(\s*([^)]*?)\s*\)/.exec(func.toString());
    return found && found[1] ? found[1].split(/,\s*/) : [];
}

// Time offset between local time and server time
var timeOffset = 0;
var lastResponseTime = Number.MAX_VALUE;

// Create a message ID starting from the local time
function createMessageId() {
    var logger = require('./logger')('util.createMessageId');
    var time = new BigInteger((new Date().getTime() + timeOffset).toString());
    if (logger.isDebugEnabled()) logger.debug('Create with time %s and offset %s', time, timeOffset);
    time = time.divide(new BigInteger((1000).toString()));
    return time.shiftLeft(32).toString();
}

// Synchronize the local time with the server time
function timeSynchronization(serverTime, requestDuration) {
    var logger = require('./logger')('util.timeSynchronization');
    var localTime = new Date().getTime();
    var responseTime = requestDuration / 2;
    if (lastResponseTime > responseTime) {
        lastResponseTime = responseTime;
        timeOffset = (serverTime + responseTime) - localTime;
        logger.info('(ServerTime %s + ServerResponseTime %s) - LocalTime %s = timeOffset %s ',
            serverTime, responseTime, localTime, timeOffset);
    } else {
        logger.info('Discarded: ServerResponseTime (%s) higher than previous one (%s) ',
             responseTime, lastResponseTime);
    }
}
<|endoftext|>