<commit_before>'use strict';

var htmlparser = require('htmlparser2');
var isObject = require('isobject');

/**
 * @see https://github.com/fb55/htmlparser2/wiki/Parser-options
 */
var defaultOptions = {lowerCaseTags: false};

/**
 * Parse html to PostHTMLTree
 * @param  {String} html
 * @param  {Object} [options=defaultOptions]
 * @return {PostHTMLTree}
 */
function postHTMLParser(html, options) {
    var bufArray = [],
        results = [];

    bufArray.last = function() {
        return this[this.length - 1];
    };

    var parser = new htmlparser.Parser({
        onprocessinginstruction: function(name, data) {
            if (name.toLowerCase() === '!doctype') {
                results.push('<' + data + '>');
            }
        },
        oncomment: function(data) {
            var comment = '<!--' + data + '-->',
                last = bufArray.last();

            if (!last) {
                results.push(comment);
                return;
            }

            last.content || (last.content = []);
            last.content.push(comment);
        },
        onopentag: function(tag, attrs) {
            var buf = { tag: tag };

            if (Object.keys(attrs).length) {
                buf.attrs = attrs;
            }

            bufArray.push(buf);
        },
        onclosetag: function() {
            var buf = bufArray.pop();

            if (!bufArray.length) {
                results.push(buf);
                return;
            }

            var last = bufArray.last();
            if (!Array.isArray(last.content)) {
                last.content = [];
            }

            last.content.push(buf);
        },
        ontext: function(text) {
            var last = bufArray.last();
            if (!last) {
                results.push(text);
                return;
            }

            last.content || (last.content = []);
            last.content.push(text);
        }
    }, options || defaultOptions);

    parser.write(html);
    parser.end();

    return results;
}

function parserWrapper() {
    var option;

    function parser(html) {
        var opt = option || defaultOptions;
        return postHTMLParser(html, opt);
    }

    if (arguments.length === 1 && isObject(arguments[0])) {
        option = arguments[0];
        return parser;
    }

    option = arguments[1];
    return parser(arguments[0]);
}

module.exports = parserWrapper;
module.exports.defaultOptions = defaultOptions;
<commit_msg>Disable lowerCaseAttributeNames option by default<commit_after>'use strict';

var htmlparser = require('htmlparser2');
var isObject = require('isobject');

/**
 * @see https://github.com/fb55/htmlparser2/wiki/Parser-options
 */
var defaultOptions = {lowerCaseTags: false, lowerCaseAttributeNames: false};

/**
 * Parse html to PostHTMLTree
 * @param  {String} html
 * @param  {Object} [options=defaultOptions]
 * @return {PostHTMLTree}
 */
function postHTMLParser(html, options) {
    var bufArray = [],
        results = [];

    bufArray.last = function() {
        return this[this.length - 1];
    };

    var parser = new htmlparser.Parser({
        onprocessinginstruction: function(name, data) {
            if (name.toLowerCase() === '!doctype') {
                results.push('<' + data + '>');
            }
        },
        oncomment: function(data) {
            var comment = '<!--' + data + '-->',
                last = bufArray.last();

            if (!last) {
                results.push(comment);
                return;
            }

            last.content || (last.content = []);
            last.content.push(comment);
        },
        onopentag: function(tag, attrs) {
            var buf = { tag: tag };

            if (Object.keys(attrs).length) {
                buf.attrs = attrs;
            }

            bufArray.push(buf);
        },
        onclosetag: function() {
            var buf = bufArray.pop();

            if (!bufArray.length) {
                results.push(buf);
                return;
            }

            var last = bufArray.last();
            if (!Array.isArray(last.content)) {
                last.content = [];
            }

            last.content.push(buf);
        },
        ontext: function(text) {
            var last = bufArray.last();
            if (!last) {
                results.push(text);
                return;
            }

            last.content || (last.content = []);
            last.content.push(text);
        }
    }, options || defaultOptions);

    parser.write(html);
    parser.end();

    return results;
}

function parserWrapper() {
    var option;

    function parser(html) {
        var opt = option || defaultOptions;
        return postHTMLParser(html, opt);
    }

    if (arguments.length === 1 && isObject(arguments[0])) {
        option = arguments[0];
        return parser;
    }

    option = arguments[1];
    return parser(arguments[0]);
}

module.exports = parserWrapper;
module.exports.defaultOptions = defaultOptions;
<|endoftext|>


<commit_before>const fs = require('fs');
const readPkg = require('read-pkg');
const path = require('path');

module.exports = (testResults) => {
  const testResultsString = JSON.stringify(testResults);

  const packagedData = readPkg.sync(process.cwd())
  const config = packagedData.jestJsonReporter || {};

  const outputFile = config.outputFile || './test-results.json';

  fs.writeFile(outputFile, testResultsString, (err) => {
    if (err) {
      console.warn('Unable to write test results JSON', err);
    }
    return 1;
  });

  return 0;
};
<commit_msg>Now returns testResults to work with newer version of jest<commit_after>const fs = require('fs');
const readPkg = require('read-pkg');
const path = require('path');

module.exports = (testResults) => {
  const testResultsString = JSON.stringify(testResults);

  const packagedData = readPkg.sync(process.cwd())
  const config = packagedData.jestJsonReporter || {};

  const outputFile = config.outputFile || './test-results.json';

  fs.writeFile(outputFile, testResultsString, (err) => {
    if (err) {
      console.warn('Unable to write test results JSON', err);
    }
  });

  return testResults;
};
<|endoftext|>


<commit_before>"use strict";

var Stream = require('./lib/stream');
var Instance = require('./lib/instance');
var parseEvent = require('./lib/parse');
var CoreInst;
var requiredAdapterMethods = ['module', 'composition', 'net'];

// set adapter api object (singleton)
module.exports = function (adapter) {

    if (CoreInst) {
        return CoreInst;
    }

    // check if adapter has all the required methods.
    requiredAdapterMethods.forEach(function (key) {
        if (typeof adapter[key] !== 'function') {
            throw new Error('Flow: "adapter.' + key + '" is not a function.');
        }
    });

    // create core instance
    CoreInst = factory(adapter);

    CoreInst.load = Instance.factory;
    CoreInst.factory = factory;
    CoreInst._name = '@';
    CoreInst._roles = {'*': true};
    CoreInst._ready = true;
    CoreInst._events = {};
    CoreInst._reset = Instance.reset;

    // save core instance in instances cache
    Instance.instances[CoreInst._name] = CoreInst;

    // init event parser with core instance
    parseEvent = parseEvent(CoreInst);

    return CoreInst;
};

// flow factory
function factory (object) {

    var clone = Object.create(object);
    clone.flow = emit;
    clone._flow = clone._flow || {};
    return clone;
}

function emit (eventName,  options, callback) {

    /*
        this.flow(flowEvent, {
            emit: 'event',
            to: 'instance',
            end: function () {}
            session: {}
        });
    */

    options = typeof options === 'function' ? {end: options} : options || {};
    options.emit = eventName;

    if (typeof options.end === 'function') {
        callback = options.end;
        delete options.end;
    }

    // return if event name is missing
    if (!options.emit) {
        throw new Error('Flow call without event name.');
    }

    options.session = options.session || {};
    options.to = options.to || this._name;

    // if request handler request call like a stream handler
    if (options.net) {
        var netStream = CoreInst.net(this, options);

        // flow callback
        if (typeof callback === 'function') {
            concatStream(netStream.o, callback);
        }

        return netStream;
    }

    // create new event stream
    var initial = {
        i: Stream.Pass(),
        o: Stream.Pass()
    };
    initial.i.cork();

    // load or get instance
    CoreInst.load(options.to, options.session, function (err, instance) {

        if (err) {
            return initial.o.emit('error', err);
        }

        // link event handler to event stream
        getEvent(instance, options, function (err, flowEvent) {

            if (err) {
                return initial.o.emit('error', err);
            }

            // setup sub streams (sequences)
            if (flowEvent.d) {
                linkStreams(instance, initial, flowEvent, options);
            }

            // end handler
            if (flowEvent.e) {
                initial.o.on('end', function () {
                    if (!initial.i._errEH) {
                        instance.flow(flowEvent.e[0], flowEvent.e[1]).i.on('ready', function () {
                            endEvent.i.end(options);
                        });
                    }
                });
            }

            // flow callback
            if (typeof callback === 'function') {
                concatStream(initial.o, callback);
            }

            initial.i.emit('ready');
            initial.ready = true;
            initial.i.uncork();
        });
    });

    return initial;
};

function concatStream (stream, callback) {
    var body = '';
    var error;

    stream.on('data', function (chunk) {
        body += chunk;
    })
    .on('error', function (err) {
        error = err;
        body = undefined;
    })
    .on('end', function () {
        callback(error, body);
    });
}

function linkStreams (instance, initial, flowEvent, options) {

    var input = initial.i;
    var first = true;
    var errEvent;
    var handleError = function (err) {

        // emit error on origin ouput stream
        initial.o.emit('error', err);

        // write error to error event
        if (flowEvent.r) {
            errEvent = errEvent || instance.flow(flowEvent.r[0], flowEvent.r[1]);
            if (errEvent.ready) {
                errEvent.i.write(err);
            } else {
                errEvent.i.on('ready', function () {
                    errEvent.i.write(err);
                });
            }

            // end error stream on initial end
            initial.o.on('end', errEvent.i.end.bind(errEvent));
        }

        // TODO keep streams piped
        // https://github.com/nodejs/node/issues/3045#issuecomment-142975237
    };

    // emit error event on initial error
    initial.i.on('error', handleError);

    flowEvent.d.forEach(function (section, index) {

        // create a new sub-stream to call handlers
        var output = Stream.Event(options);
        output.seq = section[0];
        output.on('error', handleError);

        // tee streams
        if (section[1] && section[1][0] === '|' || first) {
            input.pipe(output);
            first = false;
        }

        // pipe to next stream
        if (section[1]) {

            var shOptions = Object.assign({}, section[1][1][1]);
            shOptions.arg = options;
            shOptions.session = options.session || {};

            if (typeof section[1][1][0] === 'string') {
                var fes = instance.flow(section[1][1][0], shOptions);
                input.pipe(fes.i);
                fes.o.on('error', output.emit.bind(output, 'error'));
                fes.o.pipe(output);

            } else if (typeof section[1][1][0] === 'function') {
                section[1][1][0].call(
                    section[1][1][2],
                    {i: input, o: output},
                    shOptions,
                    handleError
                );
            }
        }

        // event stream sequence handler is the new input
        input = output;
    });

    input.pipe(initial.o);
}

var cbBuffer = {};
function getEvent (instance, options, callback) {

    // return cached event
    if (instance._events[options.emit]) {
        return callback(null, instance._events[options.emit]);
    }

    // buffer callback
    if (cbBuffer[options.emit]) {
        return cbBuffer[options.emit].push(callback);
    }

    // check if event is configured
    if (!instance._flow[options.emit]) {
        return callback(new Error('Flow.getEvent: Event "' + options.emit + '" not found on instance "' + instance._name+ '".'));
    }

    // collect all handlers for specific flow event
    cbBuffer[options.emit] = [];
    parseEvent(instance, options, function (err, event) {

        if (!err) {

            // cache event
            instance._events[options.emit] = event;
        }

        callback(err, event);

        // call buffered callbacks
        if (cbBuffer[options.emit]) {
            cbBuffer[options.emit].forEach(function (cb) {
                cb(err, event);
            });
            delete cbBuffer[options.emit];
        }
    });
}
<commit_msg>log an error if the emitter dont read the output data<commit_after>"use strict";

var Stream = require('./lib/stream');
var Instance = require('./lib/instance');
var parseEvent = require('./lib/parse');
var CoreInst;
var requiredAdapterMethods = ['module', 'composition', 'net'];

// set adapter api object (singleton)
module.exports = function (adapter) {

    if (CoreInst) {
        return CoreInst;
    }

    // check if adapter has all the required methods.
    requiredAdapterMethods.forEach(function (key) {
        if (typeof adapter[key] !== 'function') {
            throw new Error('Flow: "adapter.' + key + '" is not a function.');
        }
    });

    // create core instance
    CoreInst = factory(adapter);

    CoreInst.load = Instance.factory;
    CoreInst.factory = factory;
    CoreInst._name = '@';
    CoreInst._roles = {'*': true};
    CoreInst._ready = true;
    CoreInst._events = {};
    CoreInst._reset = Instance.reset;

    // save core instance in instances cache
    Instance.instances[CoreInst._name] = CoreInst;

    // init event parser with core instance
    parseEvent = parseEvent(CoreInst);

    return CoreInst;
};

// flow factory
function factory (object) {

    var clone = Object.create(object);
    clone.flow = emit;
    clone._flow = clone._flow || {};
    return clone;
}

function emit (eventName,  options, callback) {

    /*
        this.flow(flowEvent, {
            emit: 'event',
            to: 'instance',
            end: function () {}
            session: {}
        });
    */

    options = typeof options === 'function' ? {end: options} : options || {};
    options.emit = eventName;

    if (typeof options.end === 'function') {
        callback = options.end;
        delete options.end;
    }

    // return if event name is missing
    if (!options.emit) {
        throw new Error('Flow call without event name.');
    }

    options.session = options.session || {};
    options.to = options.to || this._name;

    // if request handler request call like a stream handler
    if (options.net) {
        var netStream = CoreInst.net(this, options);

        // flow callback
        if (typeof callback === 'function') {
            concatStream(netStream.o, callback);
        }

        return netStream;
    }

    // create new event stream
    var initial = {
        i: Stream.Pass(),
        o: Stream.Pass()
    };
    initial.i.cork();

    // load or get instance
    CoreInst.load(options.to, options.session, function (err, instance) {

        if (err) {
            return initial.o.emit('error', err);
        }

        // link event handler to event stream
        getEvent(instance, options, function (err, flowEvent) {

            if (err) {
                return initial.o.emit('error', err);
            }

            // setup sub streams (sequences)
            if (flowEvent.d) {
                linkStreams(instance, initial, flowEvent, options);
            }

            // end handler
            if (flowEvent.e) {
                initial.o.on('end', function () {
                    if (!initial.i._errEH) {
                        instance.flow(flowEvent.e[0], flowEvent.e[1]).i.on('ready', function () {
                            endEvent.i.end(options);
                        });
                    }
                });
            }

            // flow callback
            if (typeof callback === 'function') {
                concatStream(initial.o, callback);
            }

            initial.i.emit('ready');
            initial.ready = true;
            initial.i.uncork();
        });
    });

    return initial;
};

function concatStream (stream, callback) {
    var body = '';
    var error;

    stream.on('data', function (chunk) {
        body += chunk;
    })
    .on('error', function (err) {
        error = err;
        body = undefined;
    })
    .on('end', function () {
        callback(error, body);
    });
}

function linkStreams (instance, initial, flowEvent, options) {

    var input = initial.i;
    var first = true;
    var errEvent;
    var handleError = function (err) {

        // emit error on origin ouput stream
        initial.o.emit('error', err);

        // write error to error event
        if (flowEvent.r) {
            errEvent = errEvent || instance.flow(flowEvent.r[0], flowEvent.r[1]);
            if (errEvent.ready) {
                errEvent.i.write(err);
            } else {
                errEvent.i.on('ready', function () {
                    errEvent.i.write(err);
                });
            }

            // end error stream on initial end
            initial.o.on('end', errEvent.i.end.bind(errEvent));
        }

        // TODO keep streams piped
        // https://github.com/nodejs/node/issues/3045#issuecomment-142975237
    };

    // emit error event on initial error
    initial.i.on('error', handleError);

    flowEvent.d.forEach(function (section, index) {

        // create a new sub-stream to call handlers
        var output = Stream.Event(options);
        output.seq = section[0];
        output.on('error', handleError);

        // tee streams
        if (section[1] && section[1][0] === '|' || first) {
            input.pipe(output);
            first = false;
        }

        // pipe to next stream
        if (section[1]) {

            var shOptions = Object.assign({}, section[1][1][1]);
            shOptions.arg = options;
            shOptions.session = options.session || {};

            if (typeof section[1][1][0] === 'string') {
                var fes = instance.flow(section[1][1][0], shOptions);
                input.pipe(fes.i);
                fes.o.on('error', output.emit.bind(output, 'error'));
                fes.o.pipe(output);

            } else if (typeof section[1][1][0] === 'function') {
                section[1][1][0].call(
                    section[1][1][2],
                    {i: input, o: output},
                    shOptions,
                    handleError
                );
            }
        }

        // event stream sequence handler is the new input
        input = output;
    });

    input.pipe(initial.o);

    // read out data if no data listener is added, to prevent buffer overflow
    if (!initial.o._events.data) {
        initial.o.on('data', function (chunk) {
            console.error(new Error('Flow: Uncaught data chunk: Event: ', options.emit));
        });
    }
}

var cbBuffer = {};
function getEvent (instance, options, callback) {

    // return cached event
    if (instance._events[options.emit]) {
        return callback(null, instance._events[options.emit]);
    }

    // buffer callback
    if (cbBuffer[options.emit]) {
        return cbBuffer[options.emit].push(callback);
    }

    // check if event is configured
    if (!instance._flow[options.emit]) {
        return callback(new Error('Flow.getEvent: Event "' + options.emit + '" not found on instance "' + instance._name+ '".'));
    }

    // collect all handlers for specific flow event
    cbBuffer[options.emit] = [];
    parseEvent(instance, options, function (err, event) {

        if (!err) {

            // cache event
            instance._events[options.emit] = event;
        }

        callback(err, event);

        // call buffered callbacks
        if (cbBuffer[options.emit]) {
            cbBuffer[options.emit].forEach(function (cb) {
                cb(err, event);
            });
            delete cbBuffer[options.emit];
        }
    });
}
<|endoftext|>


<commit_before>var fs = require('fs');

exports.modules = {};
fs.readdirSync(__dirname + '/lib/modules').forEach(function(filename) {
	if (/\.js$/.test(filename)) {
		var name = filename.substr(0, filename.lastIndexOf('.'));
		var _module = require('./lib/modules/' + name);
		if (!_module.moduleError) {
			exports.modules.__defineGetter__(name, function() {
				return _module;
			});
		}
	}
});<commit_msg>update index.js<commit_after>module.exports = require('./lib/modules/rpcDebug');<|endoftext|>


<commit_before>const winston = require('winston');
const Telegraf = require('telegraf');
const commandParts = require('telegraf-command-parts');
const CronJob = require('cron').CronJob;
const secrets = require('./lib/secrets').load();
const {getUnreadMessages} = require('./lib/scraper');
const hashTracker = require('./lib/hashTracker');

let job;

const webmailLinkKeyboard = Telegraf.Extra
  .markdown()
  .markup((m) => m.inlineKeyboard([
    Telegraf.Markup.urlButton('Apri WebMail', 'https://webmail.studenti.unipi.it/horde/imp/')
  ]));

async function checkSecrets() {
  if (!secrets.telegram_token) {
    throw 'Non hai configurato il token del bot Telegram.';
  }
  if (!secrets.telegram_password) {
    throw 'Non hai configurato la password per il bot Telegram.';
  }
}

function formatNewMessage(message) {
  return 'Hai un nuovo messaggio! 📬\n\n' +
    `*Mittente*: ${message.from}\n` +
    `*Oggetto*: ${message.subject}\n` +
    `*Dimensione*: ${message.size}`
}

function setCronjob(bot, chatid) {
  async function doCronjob() {
    winston.info('Running cronjob for chat ID', chatid, '...');
    let unseen = (await getUnreadMessages(secrets));
    if (unseen.length > 0) {
      for (let i = 0; i < unseen.length; i++) {
        let msg = formatNewMessage(unseen[i]);
        let msgHash = hashTracker.md5(msg);
        if (!hashTracker.isSeen(msgHash)) {
          bot.telegram.sendMessage(chatid, msg, webmailLinkKeyboard);
          hashTracker.setSeen(msgHash);
        }
      }
    }
    winston.info('Done running cronjob for chat ID', chatid, '.');
  }

  // noinspection JSUnusedGlobalSymbols
  job = new CronJob({
    cronTime: '0 0 * * * *',
    onTick: doCronjob,
  });
  job.start();
}

checkSecrets()
  .then(() => {
    const bot = new Telegraf(secrets.telegram_token);

    bot.use(commandParts());

    bot.command('start', (ctx) => {
      let isPasswordOk = (ctx.state.command.args || '') === secrets.telegram_password;
      winston.info(ctx.chat.id, ctx.from.username, '/start', isPasswordOk ? 'PASS OK' : 'PASS KO');
      ctx.reply(isPasswordOk ? '👍✅ ⇒ 🔛' : '👎🚫');
      if (isPasswordOk) {
        setCronjob(bot, ctx.chat.id);
      }
    });

    bot.command('stop', (ctx) => {
      let isPasswordOk = (ctx.state.command.args || '') === secrets.telegram_password;
      winston.info(ctx.chat.id, ctx.from.username, '/stop', isPasswordOk ? 'PASS OK' : 'PASS KO');
      ctx.reply(isPasswordOk ? '👍✅ ⇒ 📴' : '👎🚫');
      if (isPasswordOk) {
        cleanup(false);
      }
    });

    bot.startPolling();
  })
  .catch((e) => {
    winston.error(e);
  });

// Cleanups & Promises handling

function cleanup(exit) {
  if (job && job.running) job.stop();
  if (exit) process.exit();
}

process.on('exit', () => {
  cleanup(true);
});
process.on('SIGINT', () => {
  cleanup(true);
});
process.on('SIGUSR1', () => {
  cleanup(true);
});
process.on('SIGUSR2', () => {
  cleanup(true);
});
process.on('uncaughtException', () => {
  cleanup(true);
});

process.on('unhandledRejection', (reason, p) => {
  console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
<commit_msg>Ping controllo keepalive<commit_after>const winston = require('winston');
const Telegraf = require('telegraf');
const commandParts = require('telegraf-command-parts');
const CronJob = require('cron').CronJob;
const secrets = require('./lib/secrets').load();
const {getUnreadMessages} = require('./lib/scraper');
const hashTracker = require('./lib/hashTracker');

let job;

const webmailLinkKeyboard = Telegraf.Extra
  .markdown()
  .markup((m) => m.inlineKeyboard([
    Telegraf.Markup.urlButton('Apri WebMail', 'https://webmail.studenti.unipi.it/horde/imp/')
  ]));

async function checkSecrets() {
  if (!secrets.telegram_token) {
    throw 'Non hai configurato il token del bot Telegram.';
  }
  if (!secrets.telegram_password) {
    throw 'Non hai configurato la password per il bot Telegram.';
  }
}

function formatNewMessage(message) {
  return 'Hai un nuovo messaggio! 📬\n\n' +
    `*Mittente*: ${message.from}\n` +
    `*Oggetto*: ${message.subject}\n` +
    `*Dimensione*: ${message.size}`
}

function setCronjob(bot, chatid) {
  async function doCronjob() {
    winston.info('Running cronjob for chat ID', chatid, '...');
    let unseen = (await getUnreadMessages(secrets));
    if (unseen.length > 0) {
      for (let i = 0; i < unseen.length; i++) {
        let msg = formatNewMessage(unseen[i]);
        let msgHash = hashTracker.md5(msg);
        if (!hashTracker.isSeen(msgHash)) {
          bot.telegram.sendMessage(chatid, msg, webmailLinkKeyboard);
          hashTracker.setSeen(msgHash);
        }
      }
    }
    winston.info('Done running cronjob for chat ID', chatid, '.');
  }

  // noinspection JSUnusedGlobalSymbols
  job = new CronJob({
    cronTime: '0 0 * * * *',
    onTick: doCronjob,
  });
  job.start();
}

checkSecrets()
  .then(() => {
    const bot = new Telegraf(secrets.telegram_token);

    bot.use(commandParts());

    bot.command('start', (ctx) => {
      let isPasswordOk = (ctx.state.command.args || '') === secrets.telegram_password;
      winston.info(ctx.chat.id, ctx.from.username, '/start', isPasswordOk ? 'PASS OK' : 'PASS KO');
      ctx.reply(isPasswordOk ? '👍✅ ⇒ 🔛' : '👎🚫');
      if (isPasswordOk) {
        setCronjob(bot, ctx.chat.id);
      }
    });

    bot.command('stop', (ctx) => {
      let isPasswordOk = (ctx.state.command.args || '') === secrets.telegram_password;
      winston.info(ctx.chat.id, ctx.from.username, '/stop', isPasswordOk ? 'PASS OK' : 'PASS KO');
      ctx.reply(isPasswordOk ? '👍✅ ⇒ 📴' : '👎🚫');
      if (isPasswordOk) {
        cleanup(false);
      }
    });

    bot.command('ping', (ctx) => {
      winston.info(ctx.chat.id, ctx.from.username, '/ping');
      ctx.reply('pong!');
    });

    bot.startPolling();
  })
  .catch((e) => {
    winston.error(e);
  });

// Cleanups & Promises handling

function cleanup(exit) {
  if (job && job.running) job.stop();
  if (exit) process.exit();
}

process.on('exit', () => {
  cleanup(true);
});
process.on('SIGINT', () => {
  cleanup(true);
});
process.on('SIGUSR1', () => {
  cleanup(true);
});
process.on('SIGUSR2', () => {
  cleanup(true);
});
process.on('uncaughtException', () => {
  cleanup(true);
});

process.on('unhandledRejection', (reason, p) => {
  console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
<|endoftext|>


<commit_before>var conf = require('./lib/conf');

var http = require('http');
var concat = require('concat-stream');
var querystring = require('querystring');

var User = require('./lib/user');
var Prompt = require('./lib/prompt');
var DialogState = require('./lib/dialog-state');

// Connect the job scheduler to the job immediately, since it needs to be
// defined before attempting to resume pending jobs.
Prompt.setJobHook(DialogState.onShouldSendPrompt);

http.createServer(function(req, res) {

  var parts = null;

  if ( (parts = /\/u\/(\+[0-9]{11})$/.exec(req.url)) ) {
    console.log('parts', parts);
    User.getOrCreateFromPhoneNumber(parts[1], function(err, userInfo) {
      if (err) {
        res.statusCode = 500;
        res.write('Error retrieving user');
        res.end();
        return;
      }

      res.statusCode = 200;

      require('./lib/db')('prompts-answered').createReadStream({
        lte: 'prompt!' + userInfo.hash + '!' + Date.now(),
        gte: 'prompt!' + userInfo.hash + '!' + 0,
        reverse: true
      })
      .on('data', function(data) {
        res.write(JSON.stringify(data.value) + '\n');
      })
      .on('end', function() {
        res.end();
      })
    })
    return;
  }

  if (req.url !== '/twebhook') {
    res.statusCode = 404;
    res.write('Unknown');
    res.end();
    return;
  }

  console.log('webhook', req.headers);

  req.pipe(concat(function(body) {
    var parsed = querystring.parse(body.toString());

    console.log('webhook body', body.toString());
    console.log('webhook body parsed', parsed);
    console.log('FROM number', parsed.From);

    if (!parsed || !parsed.From) {
      res.statusCode = 400;
      res.write('Invalid Webhook Body');
      res.end();
      return;
    }

    User.getOrCreateFromPhoneNumber(parsed.From, function(err, userInfo) {
      if (err) {
        console.log(err);
        res.statusCode = 500;
        res.write('Could not get or create user.');
        res.end();
        return;
      }
      DialogState.handle(userInfo, parsed.Body, function(err, message) {
        if (err) return twimlResponse(res, 200, err.message);
        if (message) return twimlResponse(res, 200, message);
        res.statusCode = 200;
        res.end();
      });
    });
  }))

}).listen(conf.get('port'));

function twimlResponse(res, code, msg) {
  var xml = '<?xml version="1.0" encoding="utf-8" ?>'
    + '<Response>'
    + '<Message>' + msg + '</Message>'
    + '</Response>';

  res.writeHead(200, {
    'Content-Type': 'text/xml',
    'Content-Length': xml.length
  });

  res.write(xml);
  res.end();
}<commit_msg>comment<commit_after>var conf = require('./lib/conf');

var http = require('http');
var concat = require('concat-stream');
var querystring = require('querystring');

var User = require('./lib/user');
var Prompt = require('./lib/prompt');
var DialogState = require('./lib/dialog-state');

// Connect the job scheduler to the job immediately, since it needs to be
// defined before attempting to resume pending jobs.
Prompt.setJobHook(DialogState.onShouldSendPrompt);

http.createServer(function(req, res) {

  var parts = null;

  if ( (parts = /\/u\/(\+[0-9]{11})$/.exec(req.url)) ) {
    console.log('parts', parts);
    User.getOrCreateFromPhoneNumber(parts[1], function(err, userInfo) {
      if (err) {
        res.statusCode = 500;
        res.write('Error retrieving user');
        res.end();
        return;
      }

      res.statusCode = 200;

      // Yes, this will exhaust memory eventually...
      require('./lib/db')('prompts-answered').createReadStream({
        lte: 'prompt!' + userInfo.hash + '!' + Date.now(),
        gte: 'prompt!' + userInfo.hash + '!' + 0,
        reverse: true
      })
      .on('data', function(data) {
        res.write(JSON.stringify(data.value) + '\n');
      })
      .on('end', function() {
        res.end();
      })
    })
    return;
  }

  if (req.url !== '/twebhook') {
    res.statusCode = 404;
    res.write('Unknown');
    res.end();
    return;
  }

  console.log('webhook', req.headers);

  req.pipe(concat(function(body) {
    var parsed = querystring.parse(body.toString());

    console.log('webhook body', body.toString());
    console.log('webhook body parsed', parsed);
    console.log('FROM number', parsed.From);

    if (!parsed || !parsed.From) {
      res.statusCode = 400;
      res.write('Invalid Webhook Body');
      res.end();
      return;
    }

    User.getOrCreateFromPhoneNumber(parsed.From, function(err, userInfo) {
      if (err) {
        console.log(err);
        res.statusCode = 500;
        res.write('Could not get or create user.');
        res.end();
        return;
      }
      DialogState.handle(userInfo, parsed.Body, function(err, message) {
        if (err) return twimlResponse(res, 200, err.message);
        if (message) return twimlResponse(res, 200, message);
        res.statusCode = 200;
        res.end();
      });
    });
  }))

}).listen(conf.get('port'));

function twimlResponse(res, code, msg) {
  var xml = '<?xml version="1.0" encoding="utf-8" ?>'
    + '<Response>'
    + '<Message>' + msg + '</Message>'
    + '</Response>';

  res.writeHead(200, {
    'Content-Type': 'text/xml',
    'Content-Length': xml.length
  });

  res.write(xml);
  res.end();
}<|endoftext|>


<commit_before>/**
 * 移动端富文本编辑器
 * @author ganzw@gmail.com
 * @url    https://github.com/baixuexiyang/artEditor
 */
$.fn.extend({
	_opt: {
		placeholader: '
请输入文章正文内容
',
		validHtml: [],
		limitSize: 3,
		showServer: false
	},
	artEditor: function(options) {
		var _this = this,
			styles = {
				"-webkit-user-select": "text",
				"user-select": "text",
				"overflow-y": "auto",
				"text-break": "brak-all",
				"outline": "none"
			};
		$(this).css(styles).attr("contenteditable", true);
		_this._opt = $.extend(_this._opt, options);
		try{
			$(_this._opt.imgTar).on('change', function(e) {
				var file  = e.target.files[0];
				if(Math.ceil(file.size/1024/1024) > _this._opt.limitSize) {
					console.error('文件太大');
					return;
				}
                var reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = function (f) {
                	if(_this._opt.showServer) {
                		_this.upload(f.target.result);
                		return ;
                	}
            		var img = '';
            	    _this.insertImage(img);
                };
			});
			_this.placeholderHandler();
			_this.pasteHandler();
		} catch(e) {
			console.log(e);
		}
		if(_this._opt.formInputId && $('#'+_this._opt.formInputId)[0]) {
			$(_this).on('input', function() {
				$('#'+_this._opt.formInputId).val(_this.getValue());
			});
		}
	},
	upload: function(data) {
		var _this = this, filed = _this._opt.uploadField;
		$.ajax({
			url: _this._opt.uploadUrl,
			type: 'post',
			data: $.extend(_this._opt.data, {filed: data}),
			cache: false
		})
		.then(function(res) {
			var src = _this._opt.uploadSuccess(res);
			if(src) {
				var img = '';
			    _this.insertImage(img);
			} else {
				_this._opt.uploadError(res);
			}
		});
	},
	insertImage: function(src) {
	    $(this).focus();
		var selection = window.getSelection ? window.getSelection() : document.selection;
		var range = selection.createRange ? selection.createRange() : selection.getRangeAt(0);
		if (!window.getSelection) {
		    range.pasteHTML(src);
		    range.collapse(false);
		    range.select();
		} else {
		    range.collapse(false);
		    var hasR = range.createContextualFragment(src);
		    var hasLastChild = hasR.lastChild;
		    while (hasLastChild && hasLastChild.nodeName.toLowerCase() == "br" && hasLastChild.previousSibling && hasLastChild.previousSibling.nodeName.toLowerCase() == "br") {
		        var e = hasLastChild;
		        hasLastChild = hasLastChild.previousSibling;
		        hasR.removeChild(e);
		    }
		    range.insertNode(range.createContextualFragment("
"));
		    range.insertNode(hasR);
		    if (hasLastChild) {
		        range.setEndAfter(hasLastChild);
		        range.setStartAfter(hasLastChild);
		    }
		    selection.removeAllRanges();
		    selection.addRange(range);
		}
		if(this._opt.formInputId && $('#'+this._opt.formInputId)[0]) {
			$('#'+this._opt.formInputId).val(this.getValue());
		}
	},
	pasteHandler: function() {
		var _this = this;
		$(this).on("paste", function(e) {
			console.log(e.clipboardData.items);
			var content = $(this).html();
			console.log(content);
			valiHTML = _this._opt.validHtml;
			content = content.replace(/_moz_dirty=""/gi, "").replace(/\[/g, "[[-").replace(/\]/g, "-]]").replace(/<\/ ?tr[^>]*>/gi, "[br]").replace(/<\/ ?td[^>]*>/gi, "  ").replace(/<(ul|dl|ol)[^>]*>/gi, "[br]").replace(/<(li|dd)[^>]*>/gi, "[br]").replace(/
]*>/gi, "[br]").replace(new RegExp("<(/?(?:" + valiHTML.join("|") + ")[^>]*)>", "gi"), "[$1]").replace(new RegExp(']*class="?at"?[^>]*)>', "gi"), "[span$1]").replace(/<[^>]*>/g, "").replace(/\[\[\-/g, "[").replace(/\-\]\]/g, "]").replace(new RegExp("\\[(/?(?:" + valiHTML.join("|") + "|img|span)[^\\]]*)\\]", "gi"), "<$1>");
			if (!/firefox/.test(navigator.userAgent.toLowerCase())) {
			    content = content.replace(/\r?\n/gi, "
");
			}
			$(this).html(content);
		});
	},
	placeholderHandler: function() {
		var _this = this;
		$(this).on('focus', function() {
			if($.trim($(this).html()) === _this._opt.placeholader) {
				$(this).html('');
			}
		})
		.on('blur', function() {
			if(!$(this).html()) {
				$(this).html(_this._opt.placeholader);
			}
		});

		if(!$.trim($(this).html())) {
			$(this).html(_this._opt.placeholader);
		}
	},
	getValue: function() {
		return $(this).html();
	},
	setValue: function(str) {
		$(this).html(str);
	}
});
<commit_msg>feat 解决光标在不同手机上的样式统一<commit_after>/**
 * 移动端富文本编辑器
 * @author ganzw@gmail.com
 * @url    https://github.com/baixuexiyang/artEditor
 */
$.fn.extend({
	_opt: {
		placeholader: '
请输入文章正文内容
',
		validHtml: [],
		limitSize: 3,
		showServer: false
	},
	artEditor: function(options) {
		var _this = this,
			styles = {
				"-webkit-user-select": "text",
				"user-select": "text",
				"overflow-y": "auto",
				"text-break": "brak-all",
				"outline": "none",
				"cursor": "text"
			};
		$(this).css(styles).attr("contenteditable", true);
		_this._opt = $.extend(_this._opt, options);
		try{
			$(_this._opt.imgTar).on('change', function(e) {
				var file  = e.target.files[0];
				if(Math.ceil(file.size/1024/1024) > _this._opt.limitSize) {
					console.error('文件太大');
					return;
				}
                var reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = function (f) {
                	if(_this._opt.showServer) {
                		_this.upload(f.target.result);
                		return ;
                	}
            		var img = '';
            	    _this.insertImage(img);
                };
			});
			_this.placeholderHandler();
			_this.pasteHandler();
		} catch(e) {
			console.log(e);
		}
		if(_this._opt.formInputId && $('#'+_this._opt.formInputId)[0]) {
			$(_this).on('input', function() {
				$('#'+_this._opt.formInputId).val(_this.getValue());
			});
		}
	},
	upload: function(data) {
		var _this = this, filed = _this._opt.uploadField;
		$.ajax({
			url: _this._opt.uploadUrl,
			type: 'post',
			data: $.extend(_this._opt.data, {filed: data}),
			cache: false
		})
		.then(function(res) {
			var src = _this._opt.uploadSuccess(res);
			if(src) {
				var img = '';
			    _this.insertImage(img);
			} else {
				_this._opt.uploadError(res);
			}
		});
	},
	insertImage: function(src) {
	    $(this).focus();
		var selection = window.getSelection ? window.getSelection() : document.selection;
		var range = selection.createRange ? selection.createRange() : selection.getRangeAt(0);
		if (!window.getSelection) {
		    range.pasteHTML(src);
		    range.collapse(false);
		    range.select();
		} else {
		    range.collapse(false);
		    var hasR = range.createContextualFragment(src);
		    var hasLastChild = hasR.lastChild;
		    while (hasLastChild && hasLastChild.nodeName.toLowerCase() == "br" && hasLastChild.previousSibling && hasLastChild.previousSibling.nodeName.toLowerCase() == "br") {
		        var e = hasLastChild;
		        hasLastChild = hasLastChild.previousSibling;
		        hasR.removeChild(e);
		    }
		    range.insertNode(range.createContextualFragment("
"));
		    range.insertNode(hasR);
		    if (hasLastChild) {
		        range.setEndAfter(hasLastChild);
		        range.setStartAfter(hasLastChild);
		    }
		    selection.removeAllRanges();
		    selection.addRange(range);
		}
		if(this._opt.formInputId && $('#'+this._opt.formInputId)[0]) {
			$('#'+this._opt.formInputId).val(this.getValue());
		}
	},
	pasteHandler: function() {
		var _this = this;
		$(this).on("paste", function(e) {
			console.log(e.clipboardData.items);
			var content = $(this).html();
			console.log(content);
			valiHTML = _this._opt.validHtml;
			content = content.replace(/_moz_dirty=""/gi, "").replace(/\[/g, "[[-").replace(/\]/g, "-]]").replace(/<\/ ?tr[^>]*>/gi, "[br]").replace(/<\/ ?td[^>]*>/gi, "  ").replace(/<(ul|dl|ol)[^>]*>/gi, "[br]").replace(/<(li|dd)[^>]*>/gi, "[br]").replace(/
]*>/gi, "[br]").replace(new RegExp("<(/?(?:" + valiHTML.join("|") + ")[^>]*)>", "gi"), "[$1]").replace(new RegExp(']*class="?at"?[^>]*)>', "gi"), "[span$1]").replace(/<[^>]*>/g, "").replace(/\[\[\-/g, "[").replace(/\-\]\]/g, "]").replace(new RegExp("\\[(/?(?:" + valiHTML.join("|") + "|img|span)[^\\]]*)\\]", "gi"), "<$1>");
			if (!/firefox/.test(navigator.userAgent.toLowerCase())) {
			    content = content.replace(/\r?\n/gi, "
");
			}
			$(this).html(content);
		});
	},
	placeholderHandler: function() {
		var _this = this;
		$(this).on('focus', function() {
			if($.trim($(this).html()) === _this._opt.placeholader) {
				$(this).html('');
			}
		})
		.on('blur', function() {
			if(!$(this).html()) {
				$(this).html(_this._opt.placeholader);
			}
		});

		if(!$.trim($(this).html())) {
			$(this).html(_this._opt.placeholader);
		}
	},
	getValue: function() {
		return $(this).html();
	},
	setValue: function(str) {
		$(this).html(str);
	}
});
<|endoftext|>


<commit_before>//
//  Author: Vlad Seryakov vseryakov@gmail.com
//  backendjs 2018
//

// Bootstrap backend support

bkjs.koPlugins.push(function(target) {
    $(target).find('.carousel').carousel();
    $(target).find('[data-toggle="popover"]').popover();
});

// Show/hide loading animation
bkjs.showLoading = function(op)
{
    var img = $(this.loadingElement || '.loading');
    if (!img.length) return;

    if (!this._loading) this._loading = { count: 0 };
    var state = this._loading;
    switch (op) {
    case "hide":
        if (--state.count > 0) break;
        state.count = 0;
        if (state.display == "none") img.hide(); else img.css("visibility", "hidden");
        break;

    case "show":
        if (state.count++ > 0) break;
        if (!state.display) state.display = img.css("display");
        if (state.display == "none") img.show(); else img.css("visibility", "visible");
        break;
    }
}

bkjs.showAlert = function(obj, type, text, options)
{
    if (typeof obj == "string") options = text, text = type, type = obj, obj = $("body");
    if (!text) return;
    if (!options) options = {};
    if (!bkjs._alertNum) bkjs._alertNum = 0;
    if (type == "error") type = "danger";
    var aid = "alert-" + bkjs._alertNum++;
    var html = '
';
    if (options.icon) html += '';
    html += String(typeof text == "string" ? text : text && text.message ? text.message : JSON.stringify(text)).replace(/\n/g, "
");
    html += '<button type="button" class="close" data-dismiss="alert" aria-label="Close">×</button>';
    html += "
";
    var element = options.element || ".alerts";
    if (!$(obj).find(element).length) obj = $("body");
    var alerts = $(obj).find(element);
    if (!alerts.length) return;
    if (options.hide || alerts.css("display") == "none") {
        alerts.attr("data-alert", "hide");
        alerts.show();
    }
    if (options.css) alerts.addClass(options.css);
    if (!options.append) alerts.empty();
    alerts.append(html);
    if (!options.dismiss) {
        $(obj).find("#" + aid).delay((options.delay || 3000) * (type == "danger" ? 3 : type == "warning" ? 3 : type == "info" ? 2 : 1)).fadeOut(1000, function () {
            $(this).alert('close');
        });
    }
    $(obj).find("#" + aid).on('closed.bs.alert', function() {
        bkjs.cleanupAlerts(alerts, options);
    });
    if (options.scroll) $(obj).animate({ scrollTop: 0 }, "slow");
}

bkjs.hideAlert = function(obj, options)
{
    if (!options) options = {};
    var alerts = $(obj || "body").find(options.element || ".alerts");
    if (!alerts.length) return;
    alerts.empty();
    bkjs.cleanupAlerts(alerts, options);
}

bkjs.cleanupAlerts = function(element, options)
{
    if (element.children().length) return;
    if (options.css) element.removeClass(options.css);
    if (options.hide || element.attr("data-alert") == "hide") element.hide();
    element.removeAttr("data-alert");
}

bkjs.showConfirm = function(options, callback, cancelled)
{
    if (typeof options == "string") options = { text: options };

    bootpopup({
        self: this,
        title: options.title || 'Confirm',
        show_header: options.title !== null,
        buttons: ["cancel", "ok"],
        text_ok: options.ok || "OK",
        text_cancel: options.cancel || "Cancel",
        class_ok: options.ok_class || undefined,
        class_cancel: options.cancel_class || undefined,
        content: [{ div: { html: String(options.text || "").replace(/\n/g, "
"), class: options.css || "" } }],
        ok: function() {
            if (typeof callback == "function") callback.call(this);
        },
        cancel: function() {
            if (typeof cancelled == "function") cancelled.call(this);
        }
    });
}

bkjs.showPrompt = function(options, callback)
{
    if (typeof options == "string") options = { text: options };

    var value;
    bootpopup({
        self: this,
        title: options.title || 'Prompt',
        buttons: ["cancel", "ok"],
        content: [{ input: { name: "value", label: String(options.text || "").replace(/\n/g, "
"), class: `form-control ${options.css ||""}` } }],
        ok: function(d) {
            value = d.value;
        },
        dismiss: function() {
            if (typeof callback == "function") callback.call(this, value);
        }
    });
}

bkjs.showLogin = function(options, callback)
{
    if (typeof options == "function") callback = options, options = null;
    if (!options) options = {};

    var popup;
    popup = bootpopup({
        self: this,
        id: "bkjs-login-modal",
        show_header: false,
        buttons: ["cancel", "ok"],
        text_ok: options.login_button || "Login",
        content: [
            { h4: { html: `<img src="${options.logo || "/img/logo.png"}" style="max-height: 3rem;"> ${options.title || 'Please Sign In'}`, class: "text-center py-4" } },
            { input: { name: "login", label: options.login || "Login", placeholder: options.login, autofocus: true,
                       onkeyup: function(f,e) { if (e.which == 13) { $(f).closest("form").find('input[type="password"]').focus(); e.preventDefault() } }
            } },
            { password: { name: "secret", label: options.password || "Password", placeholder: "Password",
                          onkeyup: function(f,e) { if (e.which == 13) { $(f).closest("form").trigger("submit"); e.preventDefault() } }
            } },
            options.disclaimer ? { div: { html: options.disclaimer } } : null,
        ],
        ok: function(d) {
            if (typeof options.onSubmit == "function" && !options.onSubmit(popup, d)) return false;
            var q = { login: d.login, secret: d.secret };
            if (options.url) q = { url: options.url, data: q };
            bkjs.login(q, function(err) {
                if (err) popup.showAlert("danger", err);
                if (typeof callback == "function") callback.call(self, err);
            });
            return false;
        },
    });
}

bkjs.hideLogin = function()
{
    $("#bkjs-login-modal").modal("hide");
}

$(function() {
    $(bkjs).on("bkjs.alert", function(ev, type, msg) {
        bkjs.showAlert(type, msg);
    });
    $(bkjs).on("bkjs.loading", function(ev, type) {
        bkjs.showLoading(type);
    });
});

<commit_msg>fixed prompt<commit_after>//
//  Author: Vlad Seryakov vseryakov@gmail.com
//  backendjs 2018
//

// Bootstrap backend support

bkjs.koPlugins.push(function(target) {
    $(target).find('.carousel').carousel();
    $(target).find('[data-toggle="popover"]').popover();
});

// Show/hide loading animation
bkjs.showLoading = function(op)
{
    var img = $(this.loadingElement || '.loading');
    if (!img.length) return;

    if (!this._loading) this._loading = { count: 0 };
    var state = this._loading;
    switch (op) {
    case "hide":
        if (--state.count > 0) break;
        state.count = 0;
        if (state.display == "none") img.hide(); else img.css("visibility", "hidden");
        break;

    case "show":
        if (state.count++ > 0) break;
        if (!state.display) state.display = img.css("display");
        if (state.display == "none") img.show(); else img.css("visibility", "visible");
        break;
    }
}

bkjs.showAlert = function(obj, type, text, options)
{
    if (typeof obj == "string") options = text, text = type, type = obj, obj = $("body");
    if (!text) return;
    if (!options) options = {};
    if (!bkjs._alertNum) bkjs._alertNum = 0;
    if (type == "error") type = "danger";
    var aid = "alert-" + bkjs._alertNum++;
    var html = '
';
    if (options.icon) html += '';
    html += String(typeof text == "string" ? text : text && text.message ? text.message : JSON.stringify(text)).replace(/\n/g, "
");
    html += '<button type="button" class="close" data-dismiss="alert" aria-label="Close">×</button>';
    html += "
";
    var element = options.element || ".alerts";
    if (!$(obj).find(element).length) obj = $("body");
    var alerts = $(obj).find(element);
    if (!alerts.length) return;
    if (options.hide || alerts.css("display") == "none") {
        alerts.attr("data-alert", "hide");
        alerts.show();
    }
    if (options.css) alerts.addClass(options.css);
    if (!options.append) alerts.empty();
    alerts.append(html);
    if (!options.dismiss) {
        $(obj).find("#" + aid).delay((options.delay || 3000) * (type == "danger" ? 3 : type == "warning" ? 3 : type == "info" ? 2 : 1)).fadeOut(1000, function () {
            $(this).alert('close');
        });
    }
    $(obj).find("#" + aid).on('closed.bs.alert', function() {
        bkjs.cleanupAlerts(alerts, options);
    });
    if (options.scroll) $(obj).animate({ scrollTop: 0 }, "slow");
}

bkjs.hideAlert = function(obj, options)
{
    if (!options) options = {};
    var alerts = $(obj || "body").find(options.element || ".alerts");
    if (!alerts.length) return;
    alerts.empty();
    bkjs.cleanupAlerts(alerts, options);
}

bkjs.cleanupAlerts = function(element, options)
{
    if (element.children().length) return;
    if (options.css) element.removeClass(options.css);
    if (options.hide || element.attr("data-alert") == "hide") element.hide();
    element.removeAttr("data-alert");
}

bkjs.showConfirm = function(options, callback, cancelled)
{
    if (typeof options == "string") options = { text: options };

    bootpopup({
        self: this,
        title: options.title || 'Confirm',
        show_header: options.title !== null,
        buttons: ["cancel", "ok"],
        text_ok: options.ok || "OK",
        text_cancel: options.cancel || "Cancel",
        class_ok: options.ok_class || undefined,
        class_cancel: options.cancel_class || undefined,
        content: [{ div: { html: String(options.text || "").replace(/\n/g, "
"), class: options.css || "" } }],
        ok: function() {
            if (typeof callback == "function") callback.call(this);
        },
        cancel: function() {
            if (typeof cancelled == "function") cancelled.call(this);
        }
    });
}

bkjs.showPrompt = function(options, callback)
{
    if (typeof options == "string") options = { text: options };

    var value;
    bootpopup({
        self: this,
        title: options.title || 'Prompt',
        buttons: ["cancel", "ok"],
        content: [{ input: { name: "value", label: String(options.text || "").replace(/\n/g, "
"), class: `form-control ${options.css ||""}`, value: options.value } }],
        ok: function(d) {
            value = d.value;
        },
        dismiss: function() {
            if (typeof callback == "function") callback.call(this, value);
        }
    });
}

bkjs.showLogin = function(options, callback)
{
    if (typeof options == "function") callback = options, options = null;
    if (!options) options = {};

    var popup;
    popup = bootpopup({
        self: this,
        id: "bkjs-login-modal",
        show_header: false,
        buttons: ["cancel", "ok"],
        text_ok: options.login_button || "Login",
        content: [
            { h4: { html: `<img src="${options.logo || "/img/logo.png"}" style="max-height: 3rem;"> ${options.title || 'Please Sign In'}`, class: "text-center py-4" } },
            { input: { name: "login", label: options.login || "Login", placeholder: options.login, autofocus: true,
                       onkeyup: function(f,e) { if (e.which == 13) { $(f).closest("form").find('input[type="password"]').focus(); e.preventDefault() } }
            } },
            { password: { name: "secret", label: options.password || "Password", placeholder: "Password",
                          onkeyup: function(f,e) { if (e.which == 13) { $(f).closest("form").trigger("submit"); e.preventDefault() } }
            } },
            options.disclaimer ? { div: { html: options.disclaimer } } : null,
        ],
        ok: function(d) {
            if (typeof options.onSubmit == "function" && !options.onSubmit(popup, d)) return false;
            var q = { login: d.login, secret: d.secret };
            if (options.url) q = { url: options.url, data: q };
            bkjs.login(q, function(err) {
                if (err) popup.showAlert("danger", err);
                if (typeof callback == "function") callback.call(self, err);
            });
            return false;
        },
    });
}

bkjs.hideLogin = function()
{
    $("#bkjs-login-modal").modal("hide");
}

$(function() {
    $(bkjs).on("bkjs.alert", function(ev, type, msg) {
        bkjs.showAlert(type, msg);
    });
    $(bkjs).on("bkjs.loading", function(ev, type) {
        bkjs.showLoading(type);
    });
});

<|endoftext|>


<commit_before>/**
 * Unit tests for Array.prototype
 */

// References for Resharper
/// <reference path="../qunit/qunit.js"/>
/// <reference path="../../src/firebolt.js"/>

module('Array.prototype');

test('clean', function() {
	var isClean = function(array) {
			return array.every(function(item) {
				return !$.isEmpty(item);
			});
		},
		origArray,
		cleanArray;

	origArray = [function() {}, 1, null, 0, '', 'str', undefined, false, {}, [], true, {notEmpty: true}, {}];
	cleanArray = origArray.clean();
	ok(isClean(cleanArray), 'Cleans an array with both empty and not-empty items.');
	deepEqual(cleanArray, [origArray[0], 1, 0, 'str', false, true, origArray[11]], 'Preserves the order of non-empty items.');

	origArray = [undefined, [], null, '', {}];
	cleanArray = origArray.clean();
	deepEqual(cleanArray, [], 'Returns an empty array when cleaning an array with only empty items.');

	origArray = [$.elem('div'), 100, -1, 0, false, true, 'random string', { notEmpty: true }, [1, 'a']];
	cleanArray = origArray.clean();
	ok(isClean(cleanArray), 'Cleans an array with both no empty items.');
	ok(cleanArray != origArray && cleanArray.equals(origArray), 'Returns a clone of an array with no empty items.');
});

test('clear', function() {
	var array = [1, 2, 3];

	array.clear();

	ok(array.length === 0, 'Cleared array has no length.');
	ok(array[0] === undefined && array[1] === undefined && array[3] === undefined,
		'Items are no longer in the array (the indices where the items used to be are now undefined).');
});

test('clone', function() {
	var origArray = [1, 99, '', {}, ['a', 'b'], false, /regex/],
		clonedArray = origArray.clone();

	notEqual(clonedArray, origArray, 'Cloned array is not the same as the original array.');
	deepEqual(clonedArray, origArray, 'Cloned array contains the same items as the original array.');
});

test('contains', function() {
	var array = ['a', 'b'];

	ok(array.contains('a'), 'Correctly reports that an item in the array is in fact in the array.');
	ok(array.contains('b'), 'Correctly reports that an item in the array is in fact in the array.');
	ok(!array.contains('c'), 'Correctly reports that an item not in the array is not in the array.');
	ok(!array.contains(1), 'Correctly reports that an item not in the array is not in the array.');
});

test('diff', function() {
	var array = [1, 2, 3, 4, 5];

	deepEqual(array.diff(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.diff([5, 2, 10]), [1, 3, 4], 'Correctly performs a set difference when given one array as input.');

	deepEqual(array.diff([5, 2], [1, 4]), [3], 'Correctly performs a set difference when given multiple arrays as input.');

	deepEqual((function() {
		return array.diff(arguments);
	})(1, 2, 5), [3, 4], 'Correctly performs a set difference when given an array-like object as input.');
});

test('equals', function() {
	var array = [1, 2, 3];

	ok(array.equals(array), 'Reports that an array equals itself.');

	ok(array.equals([1, 2, 3]), 'Reports that an array equals another array with the same values.');

	ok(!array.equals([3, 2, 1]), 'Reports that an array does not equal another array with the same values but in another order.');

	ok(!array.equals([1, 2]), 'Reports that an array does not equal another that has a different length.');

	ok((function() {
		return array.equals(arguments);
	})(1, 2, 3), 'Reports that an array equals another array-like object with the same values.');

	throws(array.equals, TypeError, 'Throws a TypeError when executed with undefined input.');
});

test('get', function() {
	var array = ['a', 'b', 'c'];

	ok(array.get(0) === 'a', 'Get first item with positive index.');
	ok(array.get(1) === 'b', 'Get middle item with positive index.');
	ok(array.get(2) === 'c', 'Get last item with positive index.');
	ok(array.get(3) === undefined, 'Get no item with positive out-of-range index.');
	ok(array.get(-1) === 'c', 'Get last item with negative index.');
	ok(array.get(-2) === 'b', 'Get middle item with negative index.');
	ok(array.get(-3) === 'a', 'Get first item with negative index.');
	ok(array.get(-4) === undefined, 'Get no item with out-of-range negative index.');
});

test('intersect', function() {
	var array = [1, 2, 3];

	deepEqual(array.intersect(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.intersect([2, 3, 4]), [2, 3],
		'Correctly performs a set intersection when given one array as input.');

	deepEqual(array.intersect([107, 1, 50, 2], [2, 1]), [1, 2],
		'Correctly performs a set intersection when given multiple arrays as input.');

	deepEqual((function() {
		return array.intersect(arguments);
	})(5, 2, 1), [1, 2], 'Correctly performs a set intersection when given an array-like object as input.');
});

test('remove', function() {
	var array = [1, 2, 3, 3, 4, 3];

	var retArray = array.remove(2);
	strictEqual(array, retArray, 'Returns the array.');
	deepEqual(array, [1, 3, 3, 4, 3], 'Can remove a single item from the array.');

	deepEqual(array.remove(3), [1, 4], 'Removes all instances of the input value.');

	deepEqual(array.remove(2), [1, 4], 'Removes nothing if the specified value is not in the array.');

	deepEqual(array.remove(1, 4), [], 'Removes all instances of each input value.');
});

test('union', function() {
	var array = [1, 2, 3];

	deepEqual(array.union(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.union([2, 3, 4, 5]), [1, 2, 3, 4, 5],
		'Correctly performs a set union when given one array as input.');

	deepEqual(array.union([3, 4], [50, 9]), [1, 2, 3, 4, 50, 9],
		'Correctly performs a set union when given multiple arrays as input.');

	deepEqual((function() {
		return array.union(arguments);
	})(2, 3, 4, 5), [1, 2, 3, 4, 5], 'Correctly performs a set union when given an array-like object as input.');
});

test('uniq', function() {
	deepEqual([1, 2, 3, 0].uniq(), [1, 2, 3, 0], 'Returns a clone when called on an array that already contains unique elements.');

	deepEqual([4, 2, 3, 2, 1, 4].uniq(), [4, 2, 3, 1], 'Returns a unique set when called on an unsorted array with duplicates.');

	deepEqual([1, 2, 2, 3, 4, 4].uniq(), [1, 2, 3, 4], 'Returns a unique set when called on a sorted array with duplicates.');

	deepEqual([1, 2, 2, 3, 4, 4].uniq(true), [1, 2, 3, 4],
		'Returns a unique set when called on a sorted array with duplicates and the `isSorted` parameter is `true`.');
});
<commit_msg>Finished Array unit tests with #each() and #without()<commit_after>/**
 * Unit tests for Array.prototype
 */

// References for Resharper
/// <reference path="../qunit/qunit.js"/>
/// <reference path="../../src/firebolt.js"/>

module('Array.prototype');

test('clean', function() {
	var isClean = function(array) {
			return array.every(function(item) {
				return !$.isEmpty(item);
			});
		},
		origArray,
		cleanArray;

	origArray = [function() {}, 1, null, 0, '', 'str', undefined, false, {}, [], true, {notEmpty: true}, {}];
	cleanArray = origArray.clean();
	ok(isClean(cleanArray), 'Cleans an array with both empty and not-empty items.');
	deepEqual(cleanArray, [origArray[0], 1, 0, 'str', false, true, origArray[11]], 'Preserves the order of non-empty items.');

	origArray = [undefined, [], null, '', {}];
	cleanArray = origArray.clean();
	deepEqual(cleanArray, [], 'Returns an empty array when cleaning an array with only empty items.');

	origArray = [$.elem('div'), 100, -1, 0, false, true, 'random string', { notEmpty: true }, [1, 'a']];
	cleanArray = origArray.clean();
	ok(isClean(cleanArray), 'Cleans an array with both no empty items.');
	ok(cleanArray != origArray && cleanArray.equals(origArray), 'Returns a clone of an array with no empty items.');
});

test('clear', function() {
	var array = [1, 2, 3];

	array.clear();

	ok(array.length === 0, 'Cleared array has no length.');
	ok(array[0] === undefined && array[1] === undefined && array[3] === undefined,
		'Items are no longer in the array (the indices where the items used to be are now undefined).');
});

test('clone', function() {
	var origArray = [1, 99, '', {}, ['a', 'b'], false, /regex/],
		clonedArray = origArray.clone();

	notEqual(clonedArray, origArray, 'Cloned array is not the same as the original array.');
	deepEqual(clonedArray, origArray, 'Cloned array contains the same items as the original array.');
});

test('contains', function() {
	var array = ['a', 'b'];

	ok(array.contains('a'), 'Correctly reports that an item in the array is in fact in the array.');
	ok(array.contains('b'), 'Correctly reports that an item in the array is in fact in the array.');
	ok(!array.contains('c'), 'Correctly reports that an item not in the array is not in the array.');
	ok(!array.contains(1), 'Correctly reports that an item not in the array is not in the array.');
});

test('diff', function() {
	var array = [1, 2, 3, 4, 5];

	deepEqual(array.diff(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.diff([5, 2, 10]), [1, 3, 4], 'Correctly performs a set difference when given one array as input.');

	deepEqual(array.diff([5, 2], [1, 4]), [3], 'Correctly performs a set difference when given multiple arrays as input.');

	deepEqual((function() {
		return array.diff(arguments);
	})(1, 2, 5), [3, 4], 'Correctly performs a set difference when given an array-like object as input.');
});

test('each', function() {
	var array = [],
		callback = function() {},
		thisArg;

	Firebolt.each = function(_obj, _callback, _thisArg, _isArrayLike) {
		strictEqual(_obj, array);
		strictEqual(_callback, callback);
		strictEqual(_thisArg, thisArg);
		strictEqual(_isArrayLike, 1);
	}

	array.each(callback);

	thisArg = 'this';
	array.each(callback, thisArg);
});

test('equals', function() {
	var array = [1, 2, 3];

	ok(array.equals(array), 'Reports that an array equals itself.');

	ok(array.equals([1, 2, 3]), 'Reports that an array equals another array with the same values.');

	ok(!array.equals([3, 2, 1]), 'Reports that an array does not equal another array with the same values but in another order.');

	ok(!array.equals([1, 2]), 'Reports that an array does not equal another that has a different length.');

	ok((function() {
		return array.equals(arguments);
	})(1, 2, 3), 'Reports that an array equals another array-like object with the same values.');

	throws(array.equals, TypeError, 'Throws a TypeError when executed with undefined input.');
});

test('get', function() {
	var array = ['a', 'b', 'c'];

	ok(array.get(0) === 'a', 'Get first item with positive index.');
	ok(array.get(1) === 'b', 'Get middle item with positive index.');
	ok(array.get(2) === 'c', 'Get last item with positive index.');
	ok(array.get(3) === undefined, 'Get no item with positive out-of-range index.');
	ok(array.get(-1) === 'c', 'Get last item with negative index.');
	ok(array.get(-2) === 'b', 'Get middle item with negative index.');
	ok(array.get(-3) === 'a', 'Get first item with negative index.');
	ok(array.get(-4) === undefined, 'Get no item with out-of-range negative index.');
});

test('intersect', function() {
	var array = [1, 2, 3];

	deepEqual(array.intersect(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.intersect([2, 3, 4]), [2, 3],
		'Correctly performs a set intersection when given one array as input.');

	deepEqual(array.intersect([107, 1, 50, 2], [2, 1]), [1, 2],
		'Correctly performs a set intersection when given multiple arrays as input.');

	deepEqual((function() {
		return array.intersect(arguments);
	})(5, 2, 1), [1, 2], 'Correctly performs a set intersection when given an array-like object as input.');
});

test('remove', function() {
	var array = [1, 2, 3, 3, 4, 3];

	var retArray = array.remove(2);
	strictEqual(array, retArray, 'Returns the array.');
	deepEqual(array, [1, 3, 3, 4, 3], 'Can remove a single item from the array.');

	deepEqual(array.remove(3), [1, 4], 'Removes all instances of the input value.');

	deepEqual(array.remove(2), [1, 4], 'Removes nothing if the specified value is not in the array.');

	deepEqual(array.remove(1, 4), [], 'Removes all instances of each input value.');
});

test('union', function() {
	var array = [1, 2, 3];

	deepEqual(array.union(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.union([2, 3, 4, 5]), [1, 2, 3, 4, 5],
		'Correctly performs a set union when given one array as input.');

	deepEqual(array.union([3, 4], [50, 9]), [1, 2, 3, 4, 50, 9],
		'Correctly performs a set union when given multiple arrays as input.');

	deepEqual((function() {
		return array.union(arguments);
	})(2, 3, 4, 5), [1, 2, 3, 4, 5], 'Correctly performs a set union when given an array-like object as input.');
});

test('uniq', function() {
	deepEqual([1, 2, 3, 0].uniq(), [1, 2, 3, 0], 'Returns a clone when called on an array that already contains unique elements.');

	deepEqual([4, 2, 3, 2, 1, 4].uniq(), [4, 2, 3, 1], 'Returns a unique set when called on an unsorted array with duplicates.');

	deepEqual([1, 2, 2, 3, 4, 4].uniq(), [1, 2, 3, 4], 'Returns a unique set when called on a sorted array with duplicates.');

	deepEqual([1, 2, 2, 3, 4, 4].uniq(true), [1, 2, 3, 4],
		'Returns a unique set when called on a sorted array with duplicates and the `isSorted` parameter is `true`.');
});

test('without', function() {
	var array = [1, 2, 3, 3, 4, 3];

	var retArray = array.without(2);
	ok(array != retArray, 'Returns a new array.');
	deepEqual(retArray, [1, 3, 3, 4, 3], 'Can return a new array without a single specified item.');

	deepEqual(array.without(), array, 'Returns a clone when called with no parameters.');

	deepEqual(array.without(3), [1, 2, 4], 'Can return a new array minus all instances of the input value.');

	deepEqual(array.without(1, 4, 3), [2], 'Returns a new array minus all instances of each input value.');
});
<|endoftext|>


<commit_before>window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
				  window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

////////////////////////////////////////////////
//  Utility functions
///////////////////////////////////////////////

function distance(p1, p2){
	return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
}

function midpoint(p1, p2){
	return {x: (p2.x + p1.x) / 2, y: (p2.y + p1.y) / 2};
}

function pythagorean(a, b){
	return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}

function normalizeAngle(angle, radians){
	var max = 360;
	if(radians){
		max = (Math.PI * 2);
	}
	if(angle < 0){
		angle = angle + max;
	}
	return angle % max;
}

function scaleVector(r_angle, magnitude){
	return {x: Math.cos(r_angle)*magnitude, y: Math.sin(r_angle)*magnitude};
}

function vectorAdd(/*p1, p2...*/){
	var x;
	var y;
	for(var i = 0; i < arguments.length; i++){
		if(x == null || y == null){
			x = arguments[i].x;
			y = arguments[i].y;
		}
		else{
			x += arguments[i].x;
			y += arguments[i].y;
		}
	}
	return {x: x, y: y};
}

//p1 - p2
function vectorSubtract(/* p1, p2 ...*/){
	var x;
	var y;
	for(var i = 0; i < arguments.length; i++){
		if(x == null || y == null){
			x = arguments[i].x;
			y = arguments[i].y;
		}
		else{
			x -= arguments[i].x;
			y -= arguments[i].y;
		}
	}
	return {x: x, y: y};
}

function connectTheDots(ctx, dots){
	if(!dots || dots.length < 2){
		return;
	}
	ctx.beginPath();
	for(var i = 0; i < dots.length + 1; i++){
		if(i == 0){
			ctx.moveTo(dots[i].x, dots[i].y);
		}
		else if(i == dots.length){
			ctx.lineTo(dots[0].x, dots[0].y);
			break;
		}
		else{
			ctx.lineTo(dots[i].x, dots[i].y);
		}
		//ctx.fillText(i, dots[i].x + 5, dots[i].y + 5);
	}
	ctx.stroke();

}

//a = iso sides, b = base
function isoAltitude(a, b){
	return Math.sqrt(Math.pow(a, 2) - (Math.pow(b, 2) / 4));
}

function randomInt(min, max){
	if(min === undefined){
		min = 0;
	}
	if(max === undefined){
		max = 10;
	}
	var range = max - min;
	return Math.floor((Math.random() * range)) + min;
}

function randomFloat(min, max){
	if(min === undefined){
		min = 0;
	}
	if(max === undefined){
		max = 10;
	}
	var range = max - min;
	return (Math.random() * range) + min;
}

function toDegrees(radians){
	return (radians / Math.PI) * 180;
}

function toRadians(degrees){
	return (degrees / 180) * Math.PI;
}

function getArcCoordinates ( x, y, degAngle, radius){
	//90 returns x = 0, y = 1;
	if(radius === undefined){
		radius = 1;
	}
	return {
		x: x + (Math.cos(toRadians(degAngle)) * radius),
		y: y + (Math.sin(toRadians(degAngle)) * radius)
	};
}

////////////////////////////////////////////////
//  Initialize the game
///////////////////////////////////////////////

var space = document.getElementById("space");
space.height = window.innerHeight - 50;
space.width = window.innerWidth - 50;
var game = new Game(space);

<commit_msg>support for browsers that do canvas, but don't support requestAnimationFrame (looking at you IE9)<commit_after>window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
				  window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
if(!window.requestAnimationFrame){
	window.requestAnimationFrame = function(callback){
		setTimeout(callback, 16);
	}
}
if(!window.localStorage){
	//(function(){
		var store = {};
		window.localStorage = {
			getItem : function(key){
				return store[key];
			},
			setItem : function(key, value){
				store[key] = value;
			}
		};
	//})();
}
////////////////////////////////////////////////
//  Utility functions
///////////////////////////////////////////////

function distance(p1, p2){
	return Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
}

function midpoint(p1, p2){
	return {x: (p2.x + p1.x) / 2, y: (p2.y + p1.y) / 2};
}

function pythagorean(a, b){
	return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
}

function normalizeAngle(angle, radians){
	var max = 360;
	if(radians){
		max = (Math.PI * 2);
	}
	if(angle < 0){
		angle = angle + max;
	}
	return angle % max;
}

function scaleVector(r_angle, magnitude){
	return {x: Math.cos(r_angle)*magnitude, y: Math.sin(r_angle)*magnitude};
}

function vectorAdd(/*p1, p2...*/){
	var x;
	var y;
	for(var i = 0; i < arguments.length; i++){
		if(x == null || y == null){
			x = arguments[i].x;
			y = arguments[i].y;
		}
		else{
			x += arguments[i].x;
			y += arguments[i].y;
		}
	}
	return {x: x, y: y};
}

//p1 - p2
function vectorSubtract(/* p1, p2 ...*/){
	var x;
	var y;
	for(var i = 0; i < arguments.length; i++){
		if(x == null || y == null){
			x = arguments[i].x;
			y = arguments[i].y;
		}
		else{
			x -= arguments[i].x;
			y -= arguments[i].y;
		}
	}
	return {x: x, y: y};
}

function connectTheDots(ctx, dots){
	if(!dots || dots.length < 2){
		return;
	}
	ctx.beginPath();
	for(var i = 0; i < dots.length + 1; i++){
		if(i == 0){
			ctx.moveTo(dots[i].x, dots[i].y);
		}
		else if(i == dots.length){
			ctx.lineTo(dots[0].x, dots[0].y);
			break;
		}
		else{
			ctx.lineTo(dots[i].x, dots[i].y);
		}
		//ctx.fillText(i, dots[i].x + 5, dots[i].y + 5);
	}
	ctx.stroke();

}

//a = iso sides, b = base
function isoAltitude(a, b){
	return Math.sqrt(Math.pow(a, 2) - (Math.pow(b, 2) / 4));
}

function randomInt(min, max){
	if(min === undefined){
		min = 0;
	}
	if(max === undefined){
		max = 10;
	}
	var range = max - min;
	return Math.floor((Math.random() * range)) + min;
}

function randomFloat(min, max){
	if(min === undefined){
		min = 0;
	}
	if(max === undefined){
		max = 10;
	}
	var range = max - min;
	return (Math.random() * range) + min;
}

function toDegrees(radians){
	return (radians / Math.PI) * 180;
}

function toRadians(degrees){
	return (degrees / 180) * Math.PI;
}

function getArcCoordinates ( x, y, degAngle, radius){
	//90 returns x = 0, y = 1;
	if(radius === undefined){
		radius = 1;
	}
	return {
		x: x + (Math.cos(toRadians(degAngle)) * radius),
		y: y + (Math.sin(toRadians(degAngle)) * radius)
	};
}

////////////////////////////////////////////////
//  Initialize the game
///////////////////////////////////////////////

var space = document.getElementById("space");
space.height = window.innerHeight - 50;
space.width = window.innerWidth - 50;
var game = new Game(space);

<|endoftext|>


<commit_before>define([
  'dojo/_base/declare',
  'dojo/query',
  'dojo/aspect',
  'dojo/topic',
  'dojo/_base/lang',
  'dojo/_base/json',

  "dijit/_WidgetBase",
  "dijit/_TemplatedMixin",
  "dijit/_WidgetsInTemplateMixin",
  'dijit/registry',
  'dijit/Tooltip',

  'hotplate/hotDojoLogger/logger',
  'hotplate/hotDojoStores/stores',

  ],function(
    declare
  , query
  , aspect
  , topic
  , lang
  , json

  , _WidgetBase
  , _TemplatedMixin
  , _WidgetsInTemplateMixin
  , registry
  , Tooltip

  , logger
  , stores

  ){

  var r = {}

  r.defaultSubmit = function(form, button, callback){

    return function(e){

      // Prevent the default
      e.preventDefault();

      // Make the button busy (if it is a busy button)
      if( button && typeof(button.makeReallyBusy) === 'function' ) button.makeReallyBusy();

      // Validate the form
      form.validate();
      if(! form.isValid() ){
        logger("Didn't validate, cancelling");
        if( button && typeof(button.cancel) === 'function') button.cancel();
      } else {

        // Call the callback
        callback();

        // Prevent form submission (it will be submitted through Ajax)
        return false;
      }

    }
  }

  r.UIMsg = function( button, alertBar, message ){

    return function(res){

      typeof(button) !== 'undefined'   && button   != null ? button.cancel() : null;
      typeof(alertBar) !== 'undefined' && alertBar != null ? alertBar.set('message', message) : null;
      typeof(alertBar) !== 'undefined' && alertBar != null ? alertBar.show(2000) : null;

      return res;
    }
  }

  // Function to show error messages on JsonRest put() and post() calls
  //
  r.UIErrorMsg = function( form, button, alertBar, noLogin ){

      // AJAX JsonRest failure: set error messages etc. and rethrow
      return function(err){

        var res;

        // The response hasn't been parsed yet -- get it from err.responseText
        res = hotGetResponseTextFromError(err);

        // Normalise the response as always
        res = hotFixErrorResponse(res);

        if( err.status != 422 && alertBar ) {
          alertBar.set('message','Submission failed!');
          alertBar.show();
        }

        switch(err.status){

          // ValidationError
          case 422:

            // ***********************************************
            // WATCH OUT: RESPONSE FROM THE SERVER WAS "ERROR"
            // ***********************************************

            // This array will contain the list of widgets for which the server
            // didn't like values, but didn't enforce a change before re-submitting
            var artificialErrorWidgets = [];

            // There is a message: display it
            if( res.message && alertBar ){
              alertBar.set('message','Error: ' + res.message );
              alertBar.show(); // Persistent
            }

            res.errors.forEach( function(error){

              // This cannot really happen anymore, FIXME: DELETEME
              if( error.field == ''){
              } else {

                // Get the widget by its name.
                var field = query("form#"+form.id+" input[name='" + error.field + "']");

                if(field.length && field[0].id && (widget = registry.byId( field[0].id ) ) ){

                  // Add a validator around it if the error is "persistent" (the client
                  // never wants that value again)
                  if(error.mustChange){

                    // Create a new badValue variable which contains the "bad apple"
                    var badValue = widget.get('value');

                    // Use Dojo aspects to add an extra check to the original widget's
                    // validation function, so that the client will never ever serve
                    // that function again
                    aspect.around(widget, 'validator', function(originalValidator){
                      return function(value){
                        if( value == badValue){
                          this.invalidMessage = error.message;
                          return false;
                        } else {
                          return originalValidator.call(this, value);
                        }
                      };
                    });

                  } else {

                    // Populates the array containing widgets for which an error will
                    // be raised artificially (not persistent) AFTER validation is forced
                    // (see below)
                    artificialErrorWidgets.push( { widget: widget, message: error.message });
                  }

                } else {
                  logger("Widget not found: " + error.field);
                }

              }

            });

            // Now that we might possibly have more validators attached,
            // get the form to validate again
            form.validate();

            // Artificially (VERY artificially) get the error to show. This is not due to
            // validation, so as soon as the focus is there, it will disappear
            artificialErrorWidgets.forEach(function(w){
              widget = w.widget;
              message = w.message;
              widget.set('state','Error')
              Tooltip.show(message, widget.domNode, widget.tooltipPosition, !widget.isLeftToRight());
            });

            // Cancel the submit button
            button ? button.cancel() : null;

            logger("Response came back with validation errors: " + json.toJson(res.errors) );

            // Rethrow
            throw(err);
          break;

          // BadTokenError, UnauthorizedError
          case 401:

            // Only show the alert and the problems if the noLogin flag is false. This flag is basically for
            // the login form and the recoverPassword form and for the workspace form
            // Show the error at application level

            topic.publish('globalAlert', 'Authentication problem: ' + res.message, 5000);

            if(! noLogin){

              // Show the form to retype the password
              // r.retypePasswordDialog.show();
              topic.publish( 'hotDojoSubmit/authNeeded' );
            }

            // Cancel the submit button
            button ? button.cancel() : null;

            logger("Response came back with error 401: " + res.message);

            // Rethrow
            throw(err);

          break;

          // Unauthorized
          case 403:
            topic.publish('globalAlert', 'Unauthorized: ' + res.message, 5000);

            // Cancel the submit button
            button ? button.cancel() : null;
            logger("Response came back with error 403: " + res.message);

          break;

          // Other errors
          default:

            // The user will see:
            // * message. It will be either res.message (if available) or err.message (as a fallback)
            // * status. It will be either err.status (the HTTP error code) or 'none' (as a fallback, e.g. timeouts)
            var message = res.message == '' ? err.message : res.message;
            var statusString = typeof(err.status) === 'undefined' ? '' : ' Status: ' + err.status;

            // Show the error at application level
            topic.publish( 'globalAlert', 'Application error: ' + message + statusString, 5000 );

            // Cancel the submit button
            button ? button.cancel() : null;

            logger("Respose came back with error: " + res.message + ' Status: ' + err.status );

            // Rethrow
            throw(err);
          break;

        }
        throw(err);

      }

  };

  return r;
});

         // if(! res){

          // ***************************************
          // BAD: RESPONSE FROM THE SERVER WAS EMPTY
          // This happens on connection refused
          // ***************************************

          // Cancel the submit button
          // if( button && typeof( button.cancel ) === 'function' ) button.cancel();

          // topic.publish('globalAlert', 'Empty result from server', 5000);

          // logger("Got an empty result from ajax call");
          // throw(new Error("Empty result from ajax call"));

        // } else {

          // ***************************************
          // GOOD: RESPONSE RECEIVED, AND NO ERRORS
          // ***************************************

<commit_msg>Fixed ittle problem where status was printed twice in case of status 0<commit_after>define([
  'dojo/_base/declare',
  'dojo/query',
  'dojo/aspect',
  'dojo/topic',
  'dojo/_base/lang',
  'dojo/_base/json',

  "dijit/_WidgetBase",
  "dijit/_TemplatedMixin",
  "dijit/_WidgetsInTemplateMixin",
  'dijit/registry',
  'dijit/Tooltip',

  'hotplate/hotDojoLogger/logger',
  'hotplate/hotDojoStores/stores',

  ],function(
    declare
  , query
  , aspect
  , topic
  , lang
  , json

  , _WidgetBase
  , _TemplatedMixin
  , _WidgetsInTemplateMixin
  , registry
  , Tooltip

  , logger
  , stores

  ){

  var r = {}

  r.defaultSubmit = function(form, button, callback){

    return function(e){

      // Prevent the default
      e.preventDefault();

      // Make the button busy (if it is a busy button)
      if( button && typeof(button.makeReallyBusy) === 'function' ) button.makeReallyBusy();

      // Validate the form
      form.validate();
      if(! form.isValid() ){
        logger("Didn't validate, cancelling");
        if( button && typeof(button.cancel) === 'function') button.cancel();
      } else {

        // Call the callback
        callback();

        // Prevent form submission (it will be submitted through Ajax)
        return false;
      }

    }
  }

  r.UIMsg = function( button, alertBar, message ){

    return function(res){

      typeof(button) !== 'undefined'   && button   != null ? button.cancel() : null;
      typeof(alertBar) !== 'undefined' && alertBar != null ? alertBar.set('message', message) : null;
      typeof(alertBar) !== 'undefined' && alertBar != null ? alertBar.show(2000) : null;

      return res;
    }
  }

  // Function to show error messages on JsonRest put() and post() calls
  //
  r.UIErrorMsg = function( form, button, alertBar, noLogin ){

      // AJAX JsonRest failure: set error messages etc. and rethrow
      return function(err){

        var res;

        // The response hasn't been parsed yet -- get it from err.responseText
        res = hotGetResponseTextFromError(err);

        // Normalise the response as always
        res = hotFixErrorResponse(res);

        if( err.status != 422 && alertBar ) {
          alertBar.set('message','Submission failed!');
          alertBar.show();
        }

        switch(err.status){

          // ValidationError
          case 422:

            // ***********************************************
            // WATCH OUT: RESPONSE FROM THE SERVER WAS "ERROR"
            // ***********************************************

            // This array will contain the list of widgets for which the server
            // didn't like values, but didn't enforce a change before re-submitting
            var artificialErrorWidgets = [];

            // There is a message: display it
            if( res.message && alertBar ){
              alertBar.set('message','Error: ' + res.message );
              alertBar.show(); // Persistent
            }

            res.errors.forEach( function(error){

              // This cannot really happen anymore, FIXME: DELETEME
              if( error.field == ''){
              } else {

                // Get the widget by its name.
                var field = query("form#"+form.id+" input[name='" + error.field + "']");

                if(field.length && field[0].id && (widget = registry.byId( field[0].id ) ) ){

                  // Add a validator around it if the error is "persistent" (the client
                  // never wants that value again)
                  if(error.mustChange){

                    // Create a new badValue variable which contains the "bad apple"
                    var badValue = widget.get('value');

                    // Use Dojo aspects to add an extra check to the original widget's
                    // validation function, so that the client will never ever serve
                    // that function again
                    aspect.around(widget, 'validator', function(originalValidator){
                      return function(value){
                        if( value == badValue){
                          this.invalidMessage = error.message;
                          return false;
                        } else {
                          return originalValidator.call(this, value);
                        }
                      };
                    });

                  } else {

                    // Populates the array containing widgets for which an error will
                    // be raised artificially (not persistent) AFTER validation is forced
                    // (see below)
                    artificialErrorWidgets.push( { widget: widget, message: error.message });
                  }

                } else {
                  logger("Widget not found: " + error.field);
                }

              }

            });

            // Now that we might possibly have more validators attached,
            // get the form to validate again
            form.validate();

            // Artificially (VERY artificially) get the error to show. This is not due to
            // validation, so as soon as the focus is there, it will disappear
            artificialErrorWidgets.forEach(function(w){
              widget = w.widget;
              message = w.message;
              widget.set('state','Error')
              Tooltip.show(message, widget.domNode, widget.tooltipPosition, !widget.isLeftToRight());
            });

            // Cancel the submit button
            button ? button.cancel() : null;

            logger("Response came back with validation errors: " + json.toJson(res.errors) );

            // Rethrow
            throw(err);
          break;

          // BadTokenError, UnauthorizedError
          case 401:

            // Only show the alert and the problems if the noLogin flag is false. This flag is basically for
            // the login form and the recoverPassword form and for the workspace form
            // Show the error at application level

            topic.publish('globalAlert', 'Authentication problem: ' + res.message, 5000);

            if(! noLogin){

              // Show the form to retype the password
              // r.retypePasswordDialog.show();
              topic.publish( 'hotDojoSubmit/authNeeded' );
            }

            // Cancel the submit button
            button ? button.cancel() : null;

            logger("Response came back with error 401: " + res.message);

            // Rethrow
            throw(err);

          break;

          // Unauthorized
          case 403:
            topic.publish('globalAlert', 'Unauthorized: ' + res.message, 5000);

            // Cancel the submit button
            button ? button.cancel() : null;
            logger("Response came back with error 403: " + res.message);

          break;

          // Other errors
          default:

            // The user will see:
            // * message. It will be either res.message (if available) or err.message (as a fallback)
            // * status. It will be either err.status (the HTTP error code) or 'none' (as a fallback, e.g. timeouts)
            var message = res.message == '' ? err.message : res.message;
            var statusString = typeof(err.status) === 'undefined' || err.status == 0 ? '' : ' Status: ' + err.status;

            // Show the error at application level
            topic.publish( 'globalAlert', 'Application error: ' + message + statusString, 5000 );

            // Cancel the submit button
            button ? button.cancel() : null;

            logger("Respose came back with error: " + res.message + ' Status: ' + err.status );

            // Rethrow
            throw(err);
          break;

        }
        throw(err);

      }

  };

  return r;
});

         // if(! res){

          // ***************************************
          // BAD: RESPONSE FROM THE SERVER WAS EMPTY
          // This happens on connection refused
          // ***************************************

          // Cancel the submit button
          // if( button && typeof( button.cancel ) === 'function' ) button.cancel();

          // topic.publish('globalAlert', 'Empty result from server', 5000);

          // logger("Got an empty result from ajax call");
          // throw(new Error("Empty result from ajax call"));

        // } else {

          // ***************************************
          // GOOD: RESPONSE RECEIVED, AND NO ERRORS
          // ***************************************

<|endoftext|>


<commit_before>var path = require("path");
var assert = require("assert");
var promisify = require("promisify-node");
var fse = promisify(require("fs-extra"));
var local = path.join.bind(path, __dirname);

describe("Clone", function() {
  var Repository = require(local("../../lib/repository"));
  var Clone = require(local("../../lib/clone"));
  var NodeGit = require(local("../../"));

  var http = local("../repos/http");
  var https = local("../repos/https");
  var ssh = local("../repos/ssh");
  var git = local("../repos/git");
  var file = local("../repos/file");

  var sshPublicKey = local("../id_rsa.pub");
  var sshPrivateKey = local("../id_rsa");

  // Set a reasonable timeout here now that our repository has grown.
  this.timeout(30000);

  beforeEach(function() {
    return NodeGit.Promise.all([
      fse.remove(http),
      fse.remove(https),
      fse.remove(ssh),
      fse.remove(git),
      fse.remove(file)
    ]).catch(function unhandledFunction(ex) {
      console.log(ex.message);
    });
  });

  it.skip("can clone with http", function() {
    var url = "http://github.com/nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        }
      }
    };

    return Clone.clone(url, http, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      repo.free();
    });
  });

  it("can clone with https", function() {
    var url = "https://github.com/nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        }
      }
    };

    return Clone.clone(url, https, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      repo.stateCleanup();
      repo.free();
    });
  });

  it("can clone with ssh", function() {
    var url = "git@github.com:nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        },
        credentials: function(url, userName) {
          return NodeGit.Cred.sshKeyFromAgent(userName);
        }
      }
    };

    return Clone.clone(url, ssh, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      repo.stateCleanup();
      repo.free();
    });
  });

  it("can clone with ssh while manually loading a key", function() {
    var url = "git@github.com:nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        },
        credentials: function(url, userName) {
          return NodeGit.Cred.sshKeyNew(
            userName,
            sshPublicKey,
            sshPrivateKey,
            "");
        }
      }
    };

    return Clone.clone(url, ssh, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      repo.stateCleanup();
      repo.free();
    });
  });

  it("can clone with git", function() {
    var url = "git://github.com/nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        }
      }
    };

    return Clone.clone(url, git, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      repo.stateCleanup();
      repo.free();
    });
  });

  it("can clone with filesystem", function() {
    var prefix = process.platform === "win32" ? "" : "file://";
    var url = prefix + local("../repos/empty");

    return Clone.clone(url, file).then(function(repo) {
      assert.ok(repo instanceof Repository);
      repo.stateCleanup();
      repo.free();
    });
  });

  it("will not segfault when accessing a url without username", function() {
    var url = "https://github.com/nodegit/private";

    return Clone.clone(url, git, {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        },
        credentials: function() {
          return NodeGit.Cred.userpassPlaintextNew("fake-token",
            "x-oauth-basic");
        }
      }
    }).catch(function unhandledError() { });
  });
});
<commit_msg>Change to a single clone path<commit_after>var path = require("path");
var assert = require("assert");
var promisify = require("promisify-node");
var fse = promisify(require("fs-extra"));
var local = path.join.bind(path, __dirname);

describe("Clone", function() {
  var Repository = require(local("../../lib/repository"));
  var Clone = require(local("../../lib/clone"));
  var NodeGit = require(local("../../"));

  var clonePath = local("../repos/clone");

  var sshPublicKey = local("../id_rsa.pub");
  var sshPrivateKey = local("../id_rsa");

  // Set a reasonable timeout here now that our repository has grown.
  this.timeout(30000);

  beforeEach(function() {
    return fse.remove(clonePath);
  });

  afterEach(function(done) {
    if (this.repository) {
      this.repository.free();
      delete this.repository;
    }

    process.nextTick(function() {
      gc();
      done();
    });
  });

  it.skip("can clone with http", function() {
    var test = this;
    var url = "http://github.com/nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        }
      }
    };

    return Clone.clone(url, clonePath, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      test.repository = repo;
    });
  });

  it("can clone with https", function() {
    var test = this;
    var url = "https://github.com/nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        }
      }
    };

    return Clone.clone(url, clonePath, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      test.repository = repo;
    });
  });

  it("can clone with ssh", function() {
    var test = this;
    var url = "git@github.com:nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        },
        credentials: function(url, userName) {
          return NodeGit.Cred.sshKeyFromAgent(userName);
        }
      }
    };

    return Clone.clone(url, clonePath, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      test.repository = repo;
    });
  });

  it("can clone with ssh while manually loading a key", function() {
    var test = this;
    var url = "git@github.com:nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        },
        credentials: function(url, userName) {
          return NodeGit.Cred.sshKeyNew(
            userName,
            sshPublicKey,
            sshPrivateKey,
            "");
        }
      }
    };

    return Clone.clone(url, clonePath, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
      test.repository = repo;
    });
  });

  it("can clone with git", function() {
    var url = "git://github.com/nodegit/test.git";
    var opts = {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        }
      }
    };

    return Clone.clone(url, clonePath, opts).then(function(repo) {
      assert.ok(repo instanceof Repository);
    });
  });

  it("can clone with filesystem", function() {
    var test = this;
    var prefix = process.platform === "win32" ? "" : "file://";
    var url = prefix + local("../repos/empty");

    return Clone.clone(url, clonePath).then(function(repo) {
      assert.ok(repo instanceof Repository);
      test.repository = repo;
    });
  });

  it("will not segfault when accessing a url without username", function() {
    var url = "https://github.com/nodegit/private";

    return Clone.clone(url, clonePath, {
      remoteCallbacks: {
        certificateCheck: function() {
          return 1;
        },
        credentials: function() {
          return NodeGit.Cred.userpassPlaintextNew("fake-token",
            "x-oauth-basic");
        }
      }
    }).catch(function unhandledError() { });
  });
});
<|endoftext|>