<commit_before>'use strict';

const { _request } = require('./request');
const AV = require('./av');

/**
 * @class AV.Conversation
 *
An AV.Conversation is a local representation of a LeanCloud realtime's
 * conversation. This class is a subclass of AV.Object, and retains the
 * same functionality of an AV.Object, but also extends it with various
 * conversation specific methods, like get members, creators of this conversation.
 *

 *
 * @param {String} name The name of the Role to create.
 * @param {Boolean} [options.isSystem] Set this conversation as system conversation.
 * @param {Boolean} [options.isTransient] Set this conversation as transient conversation.
 */
module.exports = AV.Object.extend('_Conversation', /** @lends AV.Conversation.prototype */ {
  constructor: function(name, options = {}) {
    AV.Object.prototype.constructor.call(this, null, null);
    this.set('name', name);
    if (options.isSystem !== undefined) {
      this.set('sys', options.isSystem ? true: false);
    }
    if (options.isTransient !== undefined) {
      this.set('tr', options.isTransient ? true : false);
    }
  },
  /**
   * Get current conversation's creator.
   *
   * @return {String}
   */
  getCreator: function() {
    return this.get('c');
  },

  /**
   * Get the last message's time.
   *
   * @return {Date}
   */
  getLastMessageAt: function() {
    return this.get('lm');
  },

  /**
   * Get this conversation's members
   *
   * @return {String[]}
   */
  getMembers: function() {
    return this.get('m');
  },

  /**
   * Add a member to this conversation
   *
   * @param {String} member
   */
  addMember: function(member) {
    return this.add('m', member);
  },

  /**
   * Get this conversation's members who set this conversation as muted.
   *
   * @return {String[]}
   */
  getMutedMembers: function() {
    return this.get('mu');
  },

  /**
   * Get this conversation's name field.
   *
   * @return String
   */
  getName: function() {
    return this.get('name');
  },

  /**
   * Returns true if this conversation is transient conversation.
   *
   * @return {Boolean}
   */
  isTransient: function() {
    return this.get('tr');
  },

  /**
   * Returns true if this conversation is system conversation.
   *
   * @return {Boolean}
   */
  isSystem: function() {
    return this.get('sys');
  },

  /**
   * Send realtime message to this conversation, using HTTP request.
   *
   * @param {String} fromClient Sender's client id.
   * @param {(String|Object)} message The message which will send to conversation.
   *     It could be a raw string, or an object with a `toJSON` method, like a
   *     realtime SDK's Message object. See more: {@link https://leancloud.cn/docs/realtime_guide-js.html#消息}
   * @param {Boolean} [options.transient] Whether send this message as transient message or not.
   * @param {String[]} [options.toClients] Ids of clients to send to. This option can be used only in system conversation.
   * @param {Object} [options.pushData] Push data to this message. See more: {@link https://url.leanapp.cn/pushData 推送消息内容}
   * @param {AuthOptions} [authOptions]
   * @return {Promise}
   */
  send: function(fromClient, message, options={}, authOptions={}) {
    if (typeof message.toJSON === 'function') {
      message = message.toJSON();
    }
    if (typeof message !== 'string') {
      message = JSON.stringify(message);
    }
    const data = {
      from_peer: fromClient,
      conv_id: this.id,
      transient: false,
      message: message,
    };
    if (options.toClients !== undefined) {
      data.to_peers = options.toClients;
    }
    if (options.transient !== undefined) {
      data.transient = options.transient ? true : false;
    }
    if (options.pushData !== undefined) {
      data.push_data = options.pushData;
    }
    return _request('rtm', 'messages', null, 'POST', data, authOptions);
  },
});
<commit_msg>docs: fix AV.Conversation's api doc index generation<commit_after>'use strict';

const { _request } = require('./request');
const AV = require('./av');

/**
 *
An AV.Conversation is a local representation of a LeanCloud realtime's
 * conversation. This class is a subclass of AV.Object, and retains the
 * same functionality of an AV.Object, but also extends it with various
 * conversation specific methods, like get members, creators of this conversation.
 *

 *
 * @class AV.Conversation
 * @param {String} name The name of the Role to create.
 * @param {Boolean} [options.isSystem] Set this conversation as system conversation.
 * @param {Boolean} [options.isTransient] Set this conversation as transient conversation.
 */
module.exports = AV.Object.extend('_Conversation', /** @lends AV.Conversation.prototype */ {
  constructor: function(name, options = {}) {
    AV.Object.prototype.constructor.call(this, null, null);
    this.set('name', name);
    if (options.isSystem !== undefined) {
      this.set('sys', options.isSystem ? true: false);
    }
    if (options.isTransient !== undefined) {
      this.set('tr', options.isTransient ? true : false);
    }
  },
  /**
   * Get current conversation's creator.
   *
   * @return {String}
   */
  getCreator: function() {
    return this.get('c');
  },

  /**
   * Get the last message's time.
   *
   * @return {Date}
   */
  getLastMessageAt: function() {
    return this.get('lm');
  },

  /**
   * Get this conversation's members
   *
   * @return {String[]}
   */
  getMembers: function() {
    return this.get('m');
  },

  /**
   * Add a member to this conversation
   *
   * @param {String} member
   */
  addMember: function(member) {
    return this.add('m', member);
  },

  /**
   * Get this conversation's members who set this conversation as muted.
   *
   * @return {String[]}
   */
  getMutedMembers: function() {
    return this.get('mu');
  },

  /**
   * Get this conversation's name field.
   *
   * @return String
   */
  getName: function() {
    return this.get('name');
  },

  /**
   * Returns true if this conversation is transient conversation.
   *
   * @return {Boolean}
   */
  isTransient: function() {
    return this.get('tr');
  },

  /**
   * Returns true if this conversation is system conversation.
   *
   * @return {Boolean}
   */
  isSystem: function() {
    return this.get('sys');
  },

  /**
   * Send realtime message to this conversation, using HTTP request.
   *
   * @param {String} fromClient Sender's client id.
   * @param {(String|Object)} message The message which will send to conversation.
   *     It could be a raw string, or an object with a `toJSON` method, like a
   *     realtime SDK's Message object. See more: {@link https://leancloud.cn/docs/realtime_guide-js.html#消息}
   * @param {Boolean} [options.transient] Whether send this message as transient message or not.
   * @param {String[]} [options.toClients] Ids of clients to send to. This option can be used only in system conversation.
   * @param {Object} [options.pushData] Push data to this message. See more: {@link https://url.leanapp.cn/pushData 推送消息内容}
   * @param {AuthOptions} [authOptions]
   * @return {Promise}
   */
  send: function(fromClient, message, options={}, authOptions={}) {
    if (typeof message.toJSON === 'function') {
      message = message.toJSON();
    }
    if (typeof message !== 'string') {
      message = JSON.stringify(message);
    }
    const data = {
      from_peer: fromClient,
      conv_id: this.id,
      transient: false,
      message: message,
    };
    if (options.toClients !== undefined) {
      data.to_peers = options.toClients;
    }
    if (options.transient !== undefined) {
      data.transient = options.transient ? true : false;
    }
    if (options.pushData !== undefined) {
      data.push_data = options.pushData;
    }
    return _request('rtm', 'messages', null, 'POST', data, authOptions);
  },
});
<|endoftext|>


<commit_before>"use strict";

var debug = require('debug')('RSS:');
var utils = require('../../lib/utils');
var color = require('irc-colors');
var Puid = require('puid');
var puid = new Puid(true);

debug('Plugin loaded');

function init(client, config) {

    var timer = 0;
    var limit = 1;
    var maxLimit = config.plugins.rss.limit || 42;
    var RssEmitter = require('./rss-emitter');
    var levelup = require('levelup');
    var db = levelup(__dirname + '/../../data/rss', {
        keyEncoding: 'binary',
        valueEncoding: 'json'
    });
    var emitter = new RssEmitter(db);

    debug('init');

    emitter.on('item:new', function (item) {

        var debug = require('debug')('RSS:ITEM:NEW:');
        var reqId = puid.generate();
        var channels;

        if (!config.plugins.rss.channels.length) {
            channels = config.irc.options.channels;
        } else {
            channels = config.plugins.rss.channels;
        }

        debug(reqId, 'CHANNELS', channels);

        channels.forEach(function (channel) {

            sendItemMessage(reqId, client, channel, item);
        });

    });

    emitter.on('error', function (error) {
        console.error('RSS:ERROR', error);
    });

    client.addListener('ping', function (raw) {

        var debug = require('debug')('RSS:PING:');
        var reqId = puid.generate();

        debug(reqId, new Date(Date.now()), 'timer=', timer);

        if (timer) {

            timer -= 1;

            debug(reqId, 'TIMER:PAUSED:REDUCED', 'timer=', timer);

            return;
        }

        timer = config.plugins.rss.timer || 7;

        debug(reqId, 'TIMER:EXPIRED', 'timer=', timer);

        config.plugins.rss.feeds.forEach(function (feed) {

            debug(reqId, 'TIMER:EMIT:FEED:IMPORT', 'feed=', feed);

            emitter.import(feed);
        });
    });

    client.addListener('message', function (nick, to, message, raw) {

        var debug = require('debug')('RSS:MESSAGE:');
        var reqId = puid.generate();
        var cmdline = utils.filterCommands(message);
        var cmd = {};

        if (!utils.isCmd('!rss', cmdline)) {

            debug(reqId, 'DISCARDED');
            return;
        }

        cmd.nick = nick ? nick.toLowerCase() : false;
        cmd.to = to ? to.toLowerCase() : false;

        cmd.cmdlineLength = cmdline.length;
        cmd.command = cmd.cmdlineLength > 1 ? cmdline[1].toLowerCase() : false;
        cmd.limit = cmd.cmdlineLength > 2 ? parseInt(cmdline[2], 10) : config.plugins.rss.defaultLimit || 3;
        cmd.isAdmin = config.admins.hasOwnProperty(cmd.nick) && config.admins[cmd.nick];
        cmd.maxLimit = config.plugins.rss.maxLimit || 42;

        debug(reqId, 'cmd=', cmd);

        if (utils.isBotMessage(to, config)) {

            debug(reqId, 'SET:PRIVMSG:' + cmd.nick.toUpperCase() + ':ON');

            cmd.to = cmd.nick;
        }

        if (cmd.isAdmin) {

            cmd.maxLimit = cmd.maxLimit * 7;

            debug(reqId, 'EXPAND:LIMIT', cmd.nick);

        }

        if (cmdline.length === 1) {

            if (config.plugins.rss.channels.indexOf(to) !== -1) {

                debug('STATUS:ON:' + to.toUpperCase());

                printStatus(reqId, client, to, true);
            } else {

                debug('STATUS:OFF:' + to.toUpperCase());

                printStatus(reqId, client, to, false);
            }

            return;
        }

        debug(reqId, "command=", cmd.command);

        switch (cmd.command) {

            case 'feeds':

                debug(reqId, 'COMMAND:FEEDS', cmd.nick);

                config.plugins.rss.feeds.forEach(function (feed) {

                    debug('feed url', feed);

                    sendMessage(reqId, client, cmd.to, ':FEED: ' + feed);
                });

                break;

            case 'import':

                if (cmd.isAdmin) {

                    debug(reqId, 'COMMAND:IMPORT:TRIGGERED', cmd.nick);

                    sendMessage(reqId, client, cmd.to, ':IMPORT: triggered by ' + cmd.nick);

                    config.plugins.rss.feeds.forEach(function (feed) {

                        debug('EMIT:FEED', feed);

                        emitter.import(feed);
                    });

                } else {
                    debug(reqId, 'COMMAND:IMPORT:TRIGGERED:DENIED', cmd.nick);

                    sendMessage(reqId, client, cmd.to, ':IMPORT: triggered by ' + cmd.nick + ' - DENIED');
                }

                break;

            case 'last':
            case 'latest':

                debug(reqId, 'COMMAND:LATEST', cmd.nick);

                db.createKeyStream({
                        gt: 'id\x00',
                        lt: 'id\x00\xff',
                        limit: cmd.limit && cmd.limit <= cmd.maxLimit ? cmd.limit : 1,
                        reverse: true
                    })
                    .on('data', function (data) {

                        db.get(data, function (err, item) {

                            sendItemMessage(reqId, client, cmd.to, item);
                        });
                    });

                break;

            case 'on':

                if (config.plugins.rss.channels.indexOf(to) === -1) {

                    debug(reqId, 'COMMAND:ON', cmd.to);

                    config.plugins.rss.channels.push(cmd.to);
                }

                printStatus(reqId, client, cmd.to, true);

                break;

            case 'off':

                if (config.plugins.rss.channels.indexOf(to) !== -1) {

                    debug(reqId, 'COMMAND:OFF', cmd.to);

                    config.plugins.rss.channels.splice(config.plugins.rss.channels.indexOf(cmd.to), 1);
                }

                printStatus(reqId, client, cmd.to, false);

                break;

            default:

                debug(reqId, 'DEFAULT:' + cmd.command.toUpperCase() + ':' + cmd.nick.toUpperCase());

                break;
        }

    });
}

var help = function help() {

    debug('HELP');

    return (
        color.bold.red.bgyellow('===================================================\n') +
        color.bold.red.bgyellow('RSS Plugin\n') +
        color.bold.red.bgyellow('===================================================\n') +
        color.bold.red.bgyellow('!rss // print status (ON or OFF)\n') +
        color.bold.red.bgyellow('!rss on // activate channel news posting\n') +
        color.bold.red.bgyellow('!rss off // deactivate channel news posting\n') +
        color.bold.red.bgyellow('!rss lastest // get latest news\n') +
        color.bold.red.bgyellow('!rss lastest 5 // get the 5 latest news\n') +
        color.bold.red.bgyellow('!rss feeds // list all feeds\n') +
        color.bold.red.bgyellow('===================================================')
    );

};

function printStatus(reqId, client, to, status) {

    debug('PRINT:STATUS', to, (status ? 'ON' : 'OFF'));

    sendMessage(reqId, client, to, (status ? color.red('ON') : color.blue('OFF')));
}

function sendMessage(reqId, client, to, msg) {

    debug(reqId, 'SENDMESSAGE:', 'to=', to, 'msg=', msg);

    client.say(to, color.green('RSS: ') + color.green(msg));
}

function sendItemMessage(reqId, client, to, item) {

    var debug = require('debug')('RSS:SENDMESSAGE:ITEM:');

    debug(reqId, 'to=', to, 'title=', (item.title ? item.title : item), 'link=', (item.link ? item.link : item.guid));

    client.say(to, color.green('RSS: ') + color.bold.brown(item.title || '*MISSING TITLE*') + ' --- -- - ' + item.link || '*MISSING LINK*');
}

module.exports = {
    init: init,
    help: help
};
<commit_msg>fix spelling mistake<commit_after>"use strict";

var debug = require('debug')('RSS:');
var utils = require('../../lib/utils');
var color = require('irc-colors');
var Puid = require('puid');
var puid = new Puid(true);

debug('Plugin loaded');

function init(client, config) {

    var timer = 0;
    var limit = 1;
    var maxLimit = config.plugins.rss.limit || 42;
    var RssEmitter = require('./rss-emitter');
    var levelup = require('levelup');
    var db = levelup(__dirname + '/../../data/rss', {
        keyEncoding: 'binary',
        valueEncoding: 'json'
    });
    var emitter = new RssEmitter(db);

    debug('init');

    emitter.on('item:new', function (item) {

        var debug = require('debug')('RSS:ITEM:NEW:');
        var reqId = puid.generate();
        var channels;

        if (!config.plugins.rss.channels.length) {
            channels = config.irc.options.channels;
        } else {
            channels = config.plugins.rss.channels;
        }

        debug(reqId, 'CHANNELS', channels);

        channels.forEach(function (channel) {

            sendItemMessage(reqId, client, channel, item);
        });

    });

    emitter.on('error', function (error) {
        console.error('RSS:ERROR', error);
    });

    client.addListener('ping', function (raw) {

        var debug = require('debug')('RSS:PING:');
        var reqId = puid.generate();

        debug(reqId, new Date(Date.now()), 'timer=', timer);

        if (timer) {

            timer -= 1;

            debug(reqId, 'TIMER:PAUSED:REDUCED', 'timer=', timer);

            return;
        }

        timer = config.plugins.rss.timer || 7;

        debug(reqId, 'TIMER:EXPIRED', 'timer=', timer);

        config.plugins.rss.feeds.forEach(function (feed) {

            debug(reqId, 'TIMER:EMIT:FEED:IMPORT', 'feed=', feed);

            emitter.import(feed);
        });
    });

    client.addListener('message', function (nick, to, message, raw) {

        var debug = require('debug')('RSS:MESSAGE:');
        var reqId = puid.generate();
        var cmdline = utils.filterCommands(message);
        var cmd = {};

        if (!utils.isCmd('!rss', cmdline)) {

            debug(reqId, 'DISCARDED');
            return;
        }

        cmd.nick = nick ? nick.toLowerCase() : false;
        cmd.to = to ? to.toLowerCase() : false;

        cmd.cmdlineLength = cmdline.length;
        cmd.command = cmd.cmdlineLength > 1 ? cmdline[1].toLowerCase() : false;
        cmd.limit = cmd.cmdlineLength > 2 ? parseInt(cmdline[2], 10) : config.plugins.rss.defaultLimit || 3;
        cmd.isAdmin = config.admins.hasOwnProperty(cmd.nick) && config.admins[cmd.nick];
        cmd.maxLimit = config.plugins.rss.maxLimit || 42;

        debug(reqId, 'cmd=', cmd);

        if (utils.isBotMessage(to, config)) {

            debug(reqId, 'SET:PRIVMSG:' + cmd.nick.toUpperCase() + ':ON');

            cmd.to = cmd.nick;
        }

        if (cmd.isAdmin) {

            cmd.maxLimit = cmd.maxLimit * 7;

            debug(reqId, 'EXPAND:LIMIT', cmd.nick);

        }

        if (cmdline.length === 1) {

            if (config.plugins.rss.channels.indexOf(to) !== -1) {

                debug('STATUS:ON:' + to.toUpperCase());

                printStatus(reqId, client, to, true);
            } else {

                debug('STATUS:OFF:' + to.toUpperCase());

                printStatus(reqId, client, to, false);
            }

            return;
        }

        debug(reqId, "command=", cmd.command);

        switch (cmd.command) {

            case 'feeds':

                debug(reqId, 'COMMAND:FEEDS', cmd.nick);

                config.plugins.rss.feeds.forEach(function (feed) {

                    debug('feed url', feed);

                    sendMessage(reqId, client, cmd.to, ':FEED: ' + feed);
                });

                break;

            case 'import':

                if (cmd.isAdmin) {

                    debug(reqId, 'COMMAND:IMPORT:TRIGGERED', cmd.nick);

                    sendMessage(reqId, client, cmd.to, ':IMPORT: triggered by ' + cmd.nick);

                    config.plugins.rss.feeds.forEach(function (feed) {

                        debug('EMIT:FEED', feed);

                        emitter.import(feed);
                    });

                } else {
                    debug(reqId, 'COMMAND:IMPORT:TRIGGERED:DENIED', cmd.nick);

                    sendMessage(reqId, client, cmd.to, ':IMPORT: triggered by ' + cmd.nick + ' - DENIED');
                }

                break;

            case 'last':
            case 'latest':

                debug(reqId, 'COMMAND:LATEST', cmd.nick);

                db.createKeyStream({
                        gt: 'id\x00',
                        lt: 'id\x00\xff',
                        limit: cmd.limit && cmd.limit <= cmd.maxLimit ? cmd.limit : 1,
                        reverse: true
                    })
                    .on('data', function (data) {

                        db.get(data, function (err, item) {

                            sendItemMessage(reqId, client, cmd.to, item);
                        });
                    });

                break;

            case 'on':

                if (config.plugins.rss.channels.indexOf(to) === -1) {

                    debug(reqId, 'COMMAND:ON', cmd.to);

                    config.plugins.rss.channels.push(cmd.to);
                }

                printStatus(reqId, client, cmd.to, true);

                break;

            case 'off':

                if (config.plugins.rss.channels.indexOf(to) !== -1) {

                    debug(reqId, 'COMMAND:OFF', cmd.to);

                    config.plugins.rss.channels.splice(config.plugins.rss.channels.indexOf(cmd.to), 1);
                }

                printStatus(reqId, client, cmd.to, false);

                break;

            default:

                debug(reqId, 'DEFAULT:' + cmd.command.toUpperCase() + ':' + cmd.nick.toUpperCase());

                break;
        }

    });
}

var help = function help() {

    debug('HELP');

    return (
        color.bold.red.bgyellow('===================================================\n') +
        color.bold.red.bgyellow('RSS Plugin\n') +
        color.bold.red.bgyellow('===================================================\n') +
        color.bold.red.bgyellow('!rss // print status (ON or OFF)\n') +
        color.bold.red.bgyellow('!rss on // activate channel news posting\n') +
        color.bold.red.bgyellow('!rss off // deactivate channel news posting\n') +
        color.bold.red.bgyellow('!rss latest // get latest news\n') +
        color.bold.red.bgyellow('!rss latest 5 // get the 5 latest news\n') +
        color.bold.red.bgyellow('!rss feeds // list all feeds\n') +
        color.bold.red.bgyellow('===================================================')
    );

};

function printStatus(reqId, client, to, status) {

    debug('PRINT:STATUS', to, (status ? 'ON' : 'OFF'));

    sendMessage(reqId, client, to, (status ? color.red('ON') : color.blue('OFF')));
}

function sendMessage(reqId, client, to, msg) {

    debug(reqId, 'SENDMESSAGE:', 'to=', to, 'msg=', msg);

    client.say(to, color.green('RSS: ') + color.green(msg));
}

function sendItemMessage(reqId, client, to, item) {

    var debug = require('debug')('RSS:SENDMESSAGE:ITEM:');

    debug(reqId, 'to=', to, 'title=', (item.title ? item.title : item), 'link=', (item.link ? item.link : item.guid));

    client.say(to, color.green('RSS: ') + color.bold.brown(item.title || '*MISSING TITLE*') + ' --- -- - ' + item.link || '*MISSING LINK*');
}

module.exports = {
    init: init,
    help: help
};
<|endoftext|>


<commit_before>var config    = require("./config/config")
  , Sequelize = require("../index")

describe('Sequelize', function() {
  it('should pass the global options correctly', function() {
    var sequelize = new Sequelize(config.database, config.username, config.password, {
      logging: false,
      define: { underscored:true }
    })
    var Model = sequelize.define('model', {name: Sequelize.STRING})
    expect(Model.options.underscored).toBeTruthy()
  })
})
<commit_msg>test for correct port and host passing<commit_after>var config    = require("./config/config")
  , Sequelize = require("../index")

describe('Sequelize', function() {
  it('should pass the global options correctly', function() {
    var sequelize = new Sequelize(config.database, config.username, config.password, {
      logging: false,
      define: { underscored:true }
    })
    var Model = sequelize.define('model', {name: Sequelize.STRING})
    expect(Model.options.underscored).toBeTruthy()
  })

  it('should correctly set the host and the port', function() {
    var options   = { host: '127.0.0.1', port: 1234 }
      , sequelize = new Sequelize(config.database, config.username, config.password, options)

    expect(sequelize.config.host).toEqual(options.host)
    expect(sequelize.config.port).toEqual(options.port)
  })
})
<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 */

function Uniform( value ) {

	if ( typeof value === 'string' ) {

		console.warn( 'THREE.Uniform: Type parameter is no longer needed.' );
		value = arguments[ 1 ];

	}

	this.value = value;

}

export { Uniform };
<commit_msg>Uniform: Added clone(). See #10178.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 */

function Uniform( value ) {

	if ( typeof value === 'string' ) {

		console.warn( 'THREE.Uniform: Type parameter is no longer needed.' );
		value = arguments[ 1 ];

	}

	this.value = value;

}

Uniform.prototype.clone = function () {

	return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() );

};

export { Uniform };
<|endoftext|>


<commit_before>import Immutable, { Map } from 'immutable';
import webApi from './web_api';
import { getEntity, read, removeEntity, swap, setEntity, updateEntity } from '../store/index';
import { syncRemoteData } from './remote_data';
import * as l from './index';
import { img as preload } from '../utils/preload_utils';
import { defaultProps } from '../ui/box/container';
import { isFieldValid, showInvalidField } from '../field/index';

export function setupLock(id, clientID, domain, options, hookRunner, emitEventFn) {
  let m = syncRemoteData(l.setup(id, clientID, domain, options, hookRunner, emitEventFn));
  preload(l.ui.logo(m) || defaultProps.logo);

  webApi.setupClient(id, clientID, domain, l.withAuthOptions(m, {
    ...options,
    popupOptions: l.ui.popupOptions(m)
  }));

  m = l.runHook(m, "didInitialize", options);

  swap(setEntity, "lock", id, m);

  if (l.auth.redirect(m)) {
    const hash = webApi.parseHash(id);
    // TODO: this leaves the hash symbol (#) in the URL, maybe we can
    // use the history API instead to remove it.
    global.location.hash = "";

    let error, result;

    if (hash) {
      if (hash.error) {
        error = hash;
      } else {
        result = hash;
      }
    }

    setTimeout(() => {
      if (result && !error) {
        l.emitAuthenticatedEvent(m, result);
      } else {
        if (!l.hasStopped(m)) {
          l.emitEvent(m, "ready", error);
        }
      }
    }, 0);
  }

}

export function openLock(id) {
  const m = read(getEntity, "lock", id);
  if (!m) {
    throw new Error("The Lock can't be opened again after it has been destroyed");
  }

  if (l.rendering(m)) {
    return false;
  }

  l.emitEvent(m, "show");

  swap(updateEntity, "lock", id, l.render);

  return true;
}

export function closeLock(id, force = false, callback = () => {}) {
  // Do nothing when the Lock can't be closed, unless closing is forced.
  let m = read(getEntity, "lock", id);
  if (!l.ui.closable(m) && !force || !l.rendering(m)) {
    return;
  }

  l.emitEvent(m, "hide")

  // If it is a modal, stop rendering an reset after a second,
  // otherwise just reset.
  if (l.ui.appendContainer(m)) {
    swap(updateEntity, "lock", id, l.stopRendering);

    setTimeout(() => {
      swap(updateEntity, "lock", id, l.reset);
      m = read(getEntity, "lock", id);
      callback(m);
    }, 1000);
  } else {
    swap(updateEntity, "lock", id, l.reset);
    callback(m);
  }
}

export function removeLock(id) {
  swap(updateEntity, "lock", id, l.stopRendering);
  swap(removeEntity, "lock", id);
}

export function updateLock(id, f) {
  return swap(updateEntity, "lock", id, f);
}

export function pinLoadingPane(id) {
  const lock = read(getEntity, "lock", id);
  if (!lock.get("isLoadingPanePinned")) {
    swap(updateEntity, "lock", id, m => m.set("isLoadingPanePinned", true));
  }
}

export function unpinLoadingPane(id) {
  swap(updateEntity, "lock", id, m => m.set("isLoadingPanePinned", false));
}

function emitEvent(id, str, ...args) {
  l.emitEvent(read(getEntity, "lock", id), str, ...args);
}

export function validateAndSubmit(id, fields = [], f) {
  swap(updateEntity, "lock", id, m => {
    const allFieldsValid = fields.reduce((r, x) => r && isFieldValid(m, x), true);
    return allFieldsValid
      ? l.setSubmitting(m, true)
      : fields.reduce((r, x) => showInvalidField(r, x), m);
  });

  const m = read(getEntity, "lock", id);
  if (l.submitting(m)) {
    f(m);
  }
}

export function logIn(id, fields, params = {}) {
  validateAndSubmit(id, fields, m => {
    webApi.logIn(id, params, (error, result) => {
      if (error) {
        setTimeout(() => logInError(id, fields, error), 250);
      } else {
        logInSuccess(id, result);
      }
    });
  });
}

export function logInSuccess(id, result) {
  const m = read(getEntity, "lock", id);

  if (!l.ui.autoclose(m)) {
    swap(updateEntity, "lock", id, m => {
      m = l.setSubmitting(m, false);
      return l.setLoggedIn(m, true);
    });
    l.emitAuthenticatedEvent(m, result);
  } else {
    closeLock(id, false, m1 => l.emitAuthenticatedEvent(m1, result));
  }
}

function logInError(id, fields, error) {
  const m = read(getEntity, "lock", id);
  const errorMessage = l.loginErrorMessage(m, error, loginType(fields));

  swap(updateEntity, "lock", id, l.setSubmitting, false, errorMessage);
}

function loginType(fields) {
  if (!fields) return;
  if (~fields.indexOf("vcode")) return "code";
  if (~fields.indexOf("username")) return "username";
  if (~fields.indexOf("email")) return "email";
}
<commit_msg>Emit redirect_error event<commit_after>import Immutable, { Map } from 'immutable';
import webApi from './web_api';
import { getEntity, read, removeEntity, swap, setEntity, updateEntity } from '../store/index';
import { syncRemoteData } from './remote_data';
import * as l from './index';
import { img as preload } from '../utils/preload_utils';
import { defaultProps } from '../ui/box/container';
import { isFieldValid, showInvalidField } from '../field/index';

export function setupLock(id, clientID, domain, options, hookRunner, emitEventFn) {
  let m = syncRemoteData(l.setup(id, clientID, domain, options, hookRunner, emitEventFn));
  preload(l.ui.logo(m) || defaultProps.logo);

  webApi.setupClient(id, clientID, domain, l.withAuthOptions(m, {
    ...options,
    popupOptions: l.ui.popupOptions(m)
  }));

  m = l.runHook(m, "didInitialize", options);

  swap(setEntity, "lock", id, m);

  if (l.auth.redirect(m)) {
    const hash = webApi.parseHash(id);
    // TODO: this leaves the hash symbol (#) in the URL, maybe we can
    // use the history API instead to remove it.
    global.location.hash = "";

    let error, result;

    if (hash) {
      if (hash.error) {
        error = hash;
      } else {
        result = hash;
      }

      setTimeout(() => {
        if (error) {
          l.emitEvent(m, "redirect_error", error);
        } else {
          l.emitAuthenticatedEvent(m, result);
        }
      }, 0);
    }

  }

}

export function openLock(id) {
  const m = read(getEntity, "lock", id);
  if (!m) {
    throw new Error("The Lock can't be opened again after it has been destroyed");
  }

  if (l.rendering(m)) {
    return false;
  }

  l.emitEvent(m, "show");

  swap(updateEntity, "lock", id, l.render);

  return true;
}

export function closeLock(id, force = false, callback = () => {}) {
  // Do nothing when the Lock can't be closed, unless closing is forced.
  let m = read(getEntity, "lock", id);
  if (!l.ui.closable(m) && !force || !l.rendering(m)) {
    return;
  }

  l.emitEvent(m, "hide")

  // If it is a modal, stop rendering an reset after a second,
  // otherwise just reset.
  if (l.ui.appendContainer(m)) {
    swap(updateEntity, "lock", id, l.stopRendering);

    setTimeout(() => {
      swap(updateEntity, "lock", id, l.reset);
      m = read(getEntity, "lock", id);
      callback(m);
    }, 1000);
  } else {
    swap(updateEntity, "lock", id, l.reset);
    callback(m);
  }
}

export function removeLock(id) {
  swap(updateEntity, "lock", id, l.stopRendering);
  swap(removeEntity, "lock", id);
}

export function updateLock(id, f) {
  return swap(updateEntity, "lock", id, f);
}

export function pinLoadingPane(id) {
  const lock = read(getEntity, "lock", id);
  if (!lock.get("isLoadingPanePinned")) {
    swap(updateEntity, "lock", id, m => m.set("isLoadingPanePinned", true));
  }
}

export function unpinLoadingPane(id) {
  swap(updateEntity, "lock", id, m => m.set("isLoadingPanePinned", false));
}

function emitEvent(id, str, ...args) {
  l.emitEvent(read(getEntity, "lock", id), str, ...args);
}

export function validateAndSubmit(id, fields = [], f) {
  swap(updateEntity, "lock", id, m => {
    const allFieldsValid = fields.reduce((r, x) => r && isFieldValid(m, x), true);
    return allFieldsValid
      ? l.setSubmitting(m, true)
      : fields.reduce((r, x) => showInvalidField(r, x), m);
  });

  const m = read(getEntity, "lock", id);
  if (l.submitting(m)) {
    f(m);
  }
}

export function logIn(id, fields, params = {}) {
  validateAndSubmit(id, fields, m => {
    webApi.logIn(id, params, (error, result) => {
      if (error) {
        setTimeout(() => logInError(id, fields, error), 250);
      } else {
        logInSuccess(id, result);
      }
    });
  });
}

export function logInSuccess(id, result) {
  const m = read(getEntity, "lock", id);

  if (!l.ui.autoclose(m)) {
    swap(updateEntity, "lock", id, m => {
      m = l.setSubmitting(m, false);
      return l.setLoggedIn(m, true);
    });
    l.emitAuthenticatedEvent(m, result);
  } else {
    closeLock(id, false, m1 => l.emitAuthenticatedEvent(m1, result));
  }
}

function logInError(id, fields, error) {
  const m = read(getEntity, "lock", id);
  const errorMessage = l.loginErrorMessage(m, error, loginType(fields));

  swap(updateEntity, "lock", id, l.setSubmitting, false, errorMessage);
}

function loginType(fields) {
  if (!fields) return;
  if (~fields.indexOf("vcode")) return "code";
  if (~fields.indexOf("username")) return "username";
  if (~fields.indexOf("email")) return "email";
}
<|endoftext|>


<commit_before>import 'es6-symbol/implement'
import Map from 'es6-map'
import Set from 'es6-set'
import assign from 'object-assign'
import findRoot from 'find-root';

import fs from 'fs'
import { dirname, basename, join } from 'path'

export const CASE_SENSITIVE_FS = !fs.existsSync(join(__dirname, 'reSOLVE.js'))

const fileExistsCache = new Map()

function cachePath(cacheKey, result) {
  fileExistsCache.set(cacheKey, { result, lastSeen: Date.now() })
}

function checkCache(cacheKey, { lifetime }) {
  if (fileExistsCache.has(cacheKey)) {
    const { result, lastSeen } = fileExistsCache.get(cacheKey)
    // check fresness
    if (Date.now() - lastSeen < (lifetime * 1000)) return result
  }
  // cache miss
  return undefined
}

// http://stackoverflow.com/a/27382838
function fileExistsWithCaseSync(filepath, cacheSettings) {
  // don't care if the FS is case-sensitive
  if (CASE_SENSITIVE_FS) return true

  // null means it resolved to a builtin
  if (filepath === null) return true

  const dir = dirname(filepath)

  let result = checkCache(filepath, cacheSettings)
  if (result != null) return result

  // base case
  if (dir === '/' || dir === '.' || /^[A-Z]:\\$/i.test(dir)) {
    result = true
  } else {
    const filenames = fs.readdirSync(dir)
    if (filenames.indexOf(basename(filepath)) === -1) {
      result = false
    } else {
      result = fileExistsWithCaseSync(dir, cacheSettings)
    }
  }
  cachePath(filepath, result)
  return result
}

export function relative(modulePath, sourceFile, settings) {

  const sourceDir = dirname(sourceFile)
      , cacheKey = sourceDir + hashObject(settings) + modulePath

  const cacheSettings = assign({
    lifetime: 30,  // seconds
  }, settings['import/cache'])

  // parse infinity
  if (cacheSettings.lifetime === '∞' || cacheSettings.lifetime === 'Infinity') {
    cacheSettings.lifetime = Infinity
  }

  const cachedPath = checkCache(cacheKey, cacheSettings)
  if (cachedPath !== undefined) return cachedPath

  function cache(path) {
    cachePath(cacheKey, path)
    return path
  }

  function withResolver(resolver, config) {

    function v1() {
      try {
        const path = resolver.resolveImport(modulePath, sourceFile, config)
        if (path === undefined) return { found: false }
        return { found: true, path }
      } catch (err) {
        return { found: false }
      }
    }

    function v2() {
      return resolver.resolve(modulePath, sourceFile, config)
    }

    switch (resolver.interfaceVersion) {
      case 2:
        return v2()

      default:
      case 1:
        return v1()
    }
  }

  const configResolvers = (settings['import/resolver']
    || { 'node': settings['import/resolve'] }) // backward compatibility

  const resolvers = resolverReducer(configResolvers, new Map())

  for (let [name, config] of resolvers) {
    const resolver = requireResolver(name, modulePath)

    let { path: fullPath, found } = withResolver(resolver, config)

    // resolvers imply file existence, this double-check just ensures the case matches
    if (found && !fileExistsWithCaseSync(fullPath, cacheSettings)) {
      // reject resolved path
      fullPath = undefined
    }

    if (found) return cache(fullPath)
  }

  return cache(undefined)
}

function resolverReducer(resolvers, map) {
  if (resolvers instanceof Array) {
    resolvers.forEach(r => resolverReducer(r, map))
    return map
  }

  if (typeof resolvers === 'string') {
    map.set(resolvers, null)
    return map
  }

  if (typeof resolvers === 'object') {
    for (let key in resolvers) {
      map.set(key, resolvers[key])
    }
    return map
  }

  throw new Error('invalid resolver config')
}

function requireResolver(name, modulePath) {
  try {
    // Try to resolve package with absolute path (/Volumes/....)
    if (path.isAbsolute(name)) {
      return require(path.resolve(name));
    }

    try {
      // Try to resolve package with path, relative to closest package.json
      const packageDir = findRoot(path.resolve(modulePath));

      return require(path.join(packageDir, name));
    } catch (err) {
      try {
        // Try to resolve package with custom name (@myorg/resolver-name)
        return require(name);
      } catch (err) {
        // Try to resolve package with conventional name
        return require(`eslint-import-resolver-${name}`)
      }
    }
  } catch (err) {
    throw new Error(`unable to load resolver "${name}".`)
  }
}

const erroredContexts = new Set()

/**
 * Given
 * @param  {string} p - module path
 * @param  {object} context - ESLint context
 * @return {string} - the full module filesystem path;
 *                    null if package is core;
 *                    undefined if not found
 */
export default function resolve(p, context) {
  try {
    return relative( p
                   , context.getFilename()
                   , context.settings
                   )
  } catch (err) {
    if (!erroredContexts.has(context)) {
      context.report({
        message: `Resolve error: ${err.message}`,
        loc: { line: 1, col: 0 },
      })
      erroredContexts.add(context)
    }
  }
}
resolve.relative = relative

import { createHash } from 'crypto'
function hashObject(object) {
  const settingsShasum = createHash('sha1')
  settingsShasum.update(JSON.stringify(object))
  return settingsShasum.digest('hex')
}
<commit_msg>Require module with absolute path without resolving<commit_after>import 'es6-symbol/implement'
import Map from 'es6-map'
import Set from 'es6-set'
import assign from 'object-assign'
import findRoot from 'find-root';

import fs from 'fs'
import { dirname, basename, join } from 'path'

export const CASE_SENSITIVE_FS = !fs.existsSync(join(__dirname, 'reSOLVE.js'))

const fileExistsCache = new Map()

function cachePath(cacheKey, result) {
  fileExistsCache.set(cacheKey, { result, lastSeen: Date.now() })
}

function checkCache(cacheKey, { lifetime }) {
  if (fileExistsCache.has(cacheKey)) {
    const { result, lastSeen } = fileExistsCache.get(cacheKey)
    // check fresness
    if (Date.now() - lastSeen < (lifetime * 1000)) return result
  }
  // cache miss
  return undefined
}

// http://stackoverflow.com/a/27382838
function fileExistsWithCaseSync(filepath, cacheSettings) {
  // don't care if the FS is case-sensitive
  if (CASE_SENSITIVE_FS) return true

  // null means it resolved to a builtin
  if (filepath === null) return true

  const dir = dirname(filepath)

  let result = checkCache(filepath, cacheSettings)
  if (result != null) return result

  // base case
  if (dir === '/' || dir === '.' || /^[A-Z]:\\$/i.test(dir)) {
    result = true
  } else {
    const filenames = fs.readdirSync(dir)
    if (filenames.indexOf(basename(filepath)) === -1) {
      result = false
    } else {
      result = fileExistsWithCaseSync(dir, cacheSettings)
    }
  }
  cachePath(filepath, result)
  return result
}

export function relative(modulePath, sourceFile, settings) {

  const sourceDir = dirname(sourceFile)
      , cacheKey = sourceDir + hashObject(settings) + modulePath

  const cacheSettings = assign({
    lifetime: 30,  // seconds
  }, settings['import/cache'])

  // parse infinity
  if (cacheSettings.lifetime === '∞' || cacheSettings.lifetime === 'Infinity') {
    cacheSettings.lifetime = Infinity
  }

  const cachedPath = checkCache(cacheKey, cacheSettings)
  if (cachedPath !== undefined) return cachedPath

  function cache(path) {
    cachePath(cacheKey, path)
    return path
  }

  function withResolver(resolver, config) {

    function v1() {
      try {
        const path = resolver.resolveImport(modulePath, sourceFile, config)
        if (path === undefined) return { found: false }
        return { found: true, path }
      } catch (err) {
        return { found: false }
      }
    }

    function v2() {
      return resolver.resolve(modulePath, sourceFile, config)
    }

    switch (resolver.interfaceVersion) {
      case 2:
        return v2()

      default:
      case 1:
        return v1()
    }
  }

  const configResolvers = (settings['import/resolver']
    || { 'node': settings['import/resolve'] }) // backward compatibility

  const resolvers = resolverReducer(configResolvers, new Map())

  for (let [name, config] of resolvers) {
    const resolver = requireResolver(name, modulePath)

    let { path: fullPath, found } = withResolver(resolver, config)

    // resolvers imply file existence, this double-check just ensures the case matches
    if (found && !fileExistsWithCaseSync(fullPath, cacheSettings)) {
      // reject resolved path
      fullPath = undefined
    }

    if (found) return cache(fullPath)
  }

  return cache(undefined)
}

function resolverReducer(resolvers, map) {
  if (resolvers instanceof Array) {
    resolvers.forEach(r => resolverReducer(r, map))
    return map
  }

  if (typeof resolvers === 'string') {
    map.set(resolvers, null)
    return map
  }

  if (typeof resolvers === 'object') {
    for (let key in resolvers) {
      map.set(key, resolvers[key])
    }
    return map
  }

  throw new Error('invalid resolver config')
}

function requireResolver(name, modulePath) {
  try {
    // Try to resolve package with absolute path (/Volumes/....)
    if (isAbsolute(name)) {
      return require(name)
    }

    try {
      // Try to resolve package with path, relative to closest package.json
      const packageDir = findRoot(path.resolve(modulePath));

      return require(path.join(packageDir, name));
    } catch (err) {
      try {
        // Try to resolve package with custom name (@myorg/resolver-name)
        return require(name);
      } catch (err) {
        // Try to resolve package with conventional name
        return require(`eslint-import-resolver-${name}`)
      }
    }
  } catch (err) {
    throw new Error(`unable to load resolver "${name}".`)
  }
}

const erroredContexts = new Set()

/**
 * Given
 * @param  {string} p - module path
 * @param  {object} context - ESLint context
 * @return {string} - the full module filesystem path;
 *                    null if package is core;
 *                    undefined if not found
 */
export default function resolve(p, context) {
  try {
    return relative( p
                   , context.getFilename()
                   , context.settings
                   )
  } catch (err) {
    if (!erroredContexts.has(context)) {
      context.report({
        message: `Resolve error: ${err.message}`,
        loc: { line: 1, col: 0 },
      })
      erroredContexts.add(context)
    }
  }
}
resolve.relative = relative

import { createHash } from 'crypto'
function hashObject(object) {
  const settingsShasum = createHash('sha1')
  settingsShasum.update(JSON.stringify(object))
  return settingsShasum.digest('hex')
}
<|endoftext|>


<commit_before>import angular from 'angular';
import coreModule from '../core/CoreModule';
import TryflightController from './TryflightController';
import {TrialFlightResourceService} from './TrialFlightResourceService';
import "angular-recaptcha";

export default angular.module('fls.tryflight', [
    coreModule.name,
    'vcRecaptcha'
])
    .controller('TryflightController', TryflightController)
    .service('TrialFlightResourceService', TrialFlightResourceService)
    .config(($routeProvider) => {
        $routeProvider
            .when('/tryflight',
                {
                    bindToController: true,
                    controllerAs: 'ctrl',
                    controller: TryflightController,
                    template: require('./tryflight.html'),
                    publicAccess: true
                });
    });
<commit_msg>renamed trial-flight route<commit_after>import angular from 'angular';
import coreModule from '../core/CoreModule';
import TryflightController from './TryflightController';
import {TrialFlightResourceService} from './TrialFlightResourceService';
import "angular-recaptcha";

export default angular.module('fls.tryflight', [
    coreModule.name,
    'vcRecaptcha'
])
    .controller('TryflightController', TryflightController)
    .service('TrialFlightResourceService', TrialFlightResourceService)
    .config(($routeProvider) => {
        $routeProvider
            .when('/trialflight',
                {
                    bindToController: true,
                    controllerAs: 'ctrl',
                    controller: TryflightController,
                    template: require('./tryflight.html'),
                    publicAccess: true
                });
    });
<|endoftext|>


<commit_before>/* global Event, HTMLElement, MutationObserver */
require('../vr-register-element');

var VRUtils = require('../vr-utils');

/**
 *
 * VRNode is the base class for all the VR markup
 * It manages loading of objects.
 *
 */

module.exports = document.registerElement(
  'vr-node',
  {
    prototype: Object.create(
      HTMLElement.prototype,
      {

        //  ----------------------------------  //
        //   Native custom elements callbacks   //
        //  ----------------------------------  //

        attachedCallback: {
          value: function () {
            var sceneEl = document.querySelector('vr-scene');
            var mixins = this.getAttribute('mixin');
            this.isVRNode = true;
            this.sceneEl = sceneEl;
            this.mixinEls = [];
            this.mixinObservers = {};
            if (mixins) { this.updateMixins(mixins); }
          },
          writable: window.debug
        },

        detachedCallback: {
          value: function () { /* no-op */ },
          writable: window.debug
        },

        attributeChangedCallback: {
          value: function (attr, oldVal, newVal) {
            // In Firefox the callback is called even if the
            // attribute value doesn't change. We return
            // if old and new values are the same
            if (attr !== 'mixin') { return; }
            if (oldVal === newVal) { return; }
            this.updateMixins(newVal, oldVal);
          },
          writable: window.debug
        },

        load: {
          value: function () {
            // To prevent emmitting the loaded event more than once
            if (this.hasLoaded) { return; }
            var event = new Event('loaded');
            this.hasLoaded = true;
            this.dispatchEvent(event);
          },
          writable: window.debug
        },

        updateMixins: {
          value: function (newMixins, oldMixins) {
            var newMixinsIds = newMixins.split(' ');
            var oldMixinsIds = oldMixins ? oldMixins.split(' ') : [];
            // To determine what listeners will be removed
            var diff = oldMixinsIds.filter(function (i) { return newMixinsIds.indexOf(i) < 0; });
            this.mixinEls = [];
            diff.forEach(this.removeMixin.bind(this));
            newMixinsIds.forEach(this.addMixin.bind(this));
          },
          writable: window.debug
        },

        addMixin: {
          value: function (mixinId) {
            var mixinEl = document.querySelector('vr-mixin#' + mixinId);
            if (!mixinEl) { return; }
            this.attachMixinListener(mixinEl);
            this.mixinEls.push(mixinEl);
          }
        },

        removeMixin: {
          value: function (mixinId) {
            var mixinEls = this.mixinEls;
            var mixinEl;
            var i;
            for (i = 0; i < mixinEls.length; ++i) {
              mixinEl = mixinEls[i];
              if (mixinId === mixinEl.id) {
                mixinEls.splice(i, 1);
                break;
              }
            }
            this.removeMixinListener(mixinId);
          },
          writable: window.debug
        },

        removeMixinListener: {
          value: function (mixinId) {
            var observer = this.mixinObservers[mixinId];
            if (!observer) { return; }
            observer.disconnect();
            this.mixinObservers[mixinId] = null;
          },
          writable: window.debug
        },

        attachMixinListener: {
          value: function (mixinEl) {
            var self = this;
            var mixinId = mixinEl.id;
            var currentObserver = this.mixinObservers[mixinId];
            if (!mixinEl) { return; }
            if (currentObserver) { return; }
            var observer = new MutationObserver(function (mutations) {
              var attr = mutations[0].attributeName;
              self.applyMixin(attr);
            });
            var config = { attributes: true };
            observer.observe(mixinEl, config);
            this.mixinObservers[mixinId] = observer;
          },
          writable: window.debug
        },

        applyMixin: {
          value: function () { /* no-op */ },
          writable: window.debug
        },

        /**
         * Emits a DOM event.
         *
         * @param {String} name
         *   Name of event (use a space-delimited string for multiple events).
         * @param {Object} detail
         *   Custom data (optional) to pass as `detail` if the event is to
         *   be a `CustomEvent`.
         */
        emit: {
          value: function (name, detail) {
            var self = this;
            return name.split(' ').map(function (eventName) {
              return VRUtils.fireEvent(self, eventName, detail);
            });
          },
          writable: window.debug
        },

        /**
         * Returns a closure that emits a DOM event.
         *
         * @param {String} name
         *   Name of event (use a space-delimited string for multiple events).
         * @param {Object} detail
         *   Custom data (optional) to pass as `detail` if the event is to
         *   be a `CustomEvent`.
         */
        emitter: {
          value: function (name, detail) {
            var self = this;
            return function () {
              self.emit(name, detail);
            };
          },
          writable: window.debug
        }
      })
  }
);
<commit_msg>Prevents components updates when the element is not attached to the DOM and not yet initialized through the attachedCallback method<commit_after>/* global Event, HTMLElement, MutationObserver */
require('../vr-register-element');

var VRUtils = require('../vr-utils');

/**
 *
 * VRNode is the base class for all the VR markup
 * It manages loading of objects.
 *
 */

module.exports = document.registerElement(
  'vr-node',
  {
    prototype: Object.create(
      HTMLElement.prototype,
      {

        //  ----------------------------------  //
        //   Native custom elements callbacks   //
        //  ----------------------------------  //

        attachedCallback: {
          value: function () {
            var sceneEl = document.querySelector('vr-scene');
            var mixins = this.getAttribute('mixin');
            this.isVRNode = true;
            this.sceneEl = sceneEl;
            this.mixinEls = [];
            this.mixinObservers = {};
            if (mixins) { this.updateMixins(mixins); }
          },
          writable: window.debug
        },

        detachedCallback: {
          value: function () { /* no-op */ },
          writable: window.debug
        },

        attributeChangedCallback: {
          value: function (attr, oldVal, newVal) {
            // When creating objects programmatically and setting attributes
            // the object is not part of the scene until is inserted in the
            // DOM
            if (!this.hasLoaded) { return; }
            // In Firefox the callback is called even if the
            // attribute value doesn't change. We return
            // if old and new values are the same
            if (attr !== 'mixin') { return; }
            if (oldVal === newVal) { return; }
            this.updateMixins(newVal, oldVal);
          },
          writable: window.debug
        },

        load: {
          value: function () {
            // To prevent emmitting the loaded event more than once
            if (this.hasLoaded) { return; }
            var event = new Event('loaded');
            this.hasLoaded = true;
            this.dispatchEvent(event);
          },
          writable: window.debug
        },

        updateMixins: {
          value: function (newMixins, oldMixins) {
            var newMixinsIds = newMixins.split(' ');
            var oldMixinsIds = oldMixins ? oldMixins.split(' ') : [];
            // To determine what listeners will be removed
            var diff = oldMixinsIds.filter(function (i) { return newMixinsIds.indexOf(i) < 0; });
            this.mixinEls = [];
            diff.forEach(this.removeMixin.bind(this));
            newMixinsIds.forEach(this.addMixin.bind(this));
          },
          writable: window.debug
        },

        addMixin: {
          value: function (mixinId) {
            var mixinEl = document.querySelector('vr-mixin#' + mixinId);
            if (!mixinEl) { return; }
            this.attachMixinListener(mixinEl);
            this.mixinEls.push(mixinEl);
          }
        },

        removeMixin: {
          value: function (mixinId) {
            var mixinEls = this.mixinEls;
            var mixinEl;
            var i;
            for (i = 0; i < mixinEls.length; ++i) {
              mixinEl = mixinEls[i];
              if (mixinId === mixinEl.id) {
                mixinEls.splice(i, 1);
                break;
              }
            }
            this.removeMixinListener(mixinId);
          },
          writable: window.debug
        },

        removeMixinListener: {
          value: function (mixinId) {
            var observer = this.mixinObservers[mixinId];
            if (!observer) { return; }
            observer.disconnect();
            this.mixinObservers[mixinId] = null;
          },
          writable: window.debug
        },

        attachMixinListener: {
          value: function (mixinEl) {
            var self = this;
            var mixinId = mixinEl.id;
            var currentObserver = this.mixinObservers[mixinId];
            if (!mixinEl) { return; }
            if (currentObserver) { return; }
            var observer = new MutationObserver(function (mutations) {
              var attr = mutations[0].attributeName;
              self.applyMixin(attr);
            });
            var config = { attributes: true };
            observer.observe(mixinEl, config);
            this.mixinObservers[mixinId] = observer;
          },
          writable: window.debug
        },

        applyMixin: {
          value: function () { /* no-op */ },
          writable: window.debug
        },

        /**
         * Emits a DOM event.
         *
         * @param {String} name
         *   Name of event (use a space-delimited string for multiple events).
         * @param {Object} detail
         *   Custom data (optional) to pass as `detail` if the event is to
         *   be a `CustomEvent`.
         */
        emit: {
          value: function (name, detail) {
            var self = this;
            return name.split(' ').map(function (eventName) {
              return VRUtils.fireEvent(self, eventName, detail);
            });
          },
          writable: window.debug
        },

        /**
         * Returns a closure that emits a DOM event.
         *
         * @param {String} name
         *   Name of event (use a space-delimited string for multiple events).
         * @param {Object} detail
         *   Custom data (optional) to pass as `detail` if the event is to
         *   be a `CustomEvent`.
         */
        emitter: {
          value: function (name, detail) {
            var self = this;
            return function () {
              self.emit(name, detail);
            };
          },
          writable: window.debug
        }
      })
  }
);
<|endoftext|>


<commit_before>define(["js/data/Entity", "sprd/entity/ShippingState", "sprd/entity/ShippingCountry", "sprd/entity/Person"], function (Entity, ShippingState, ShippingCountry, Person) {

    var ADDRESS_TYPES = {
        PACKSTATION: "PACKSTATION",
        PRIVATE: "PRIVATE"
    };

    var Address = Entity.inherit("sprd.entity.Address", {

        defaults: {
            type: ADDRESS_TYPES.PRIVATE,
            company: null,
            person: Person,

            street: null,
            houseNumber: null,
            streetAnnex: null,
            city: null,
            state: null,
            country: null,
            zipCode: null,
            email: null,
            phone: null,
            fax: null,

            root: null,
            shippingCountries: "{root.shippingCountries()}"
        },

        schema: {
            type: {
                type: String,
                required: true
            },
            company: {
                type: String,
                required: false
            },

            person: Person,

            street: String,
            houseNumber: String,
            streetAnnex: {
                type: String,
                required: false
            },
            city: String,
            state: {
                type: ShippingState,
                required: function () {
                    return this.get("country.isoCode") === "US";
                }
            },
            country: ShippingCountry,
            zipCode: String,

            email: String,
            phone: {
                type: String,
                required: false
            },
            fax: {
                type: String,
                required: false
            }
        }
    });

    Address.ADDRESS_TYPES = ADDRESS_TYPES;

    return Address;
});
<commit_msg>removed housenumber from address<commit_after>define(["js/data/Entity", "sprd/entity/ShippingState", "sprd/entity/ShippingCountry", "sprd/entity/Person"], function (Entity, ShippingState, ShippingCountry, Person) {

    var ADDRESS_TYPES = {
        PACKSTATION: "PACKSTATION",
        PRIVATE: "PRIVATE"
    };

    var Address = Entity.inherit("sprd.entity.Address", {

        defaults: {
            type: ADDRESS_TYPES.PRIVATE,
            company: null,
            person: Person,

            street: null,
            streetAnnex: null,
            city: null,
            state: null,
            country: null,
            zipCode: null,
            email: null,
            phone: null,
            fax: null,

            root: null,
            shippingCountries: "{root.shippingCountries()}"
        },

        schema: {
            type: {
                type: String,
                required: true
            },
            company: {
                type: String,
                required: false
            },

            person: Person,

            street: String,

            streetAnnex: {
                type: String,
                required: false
            },
            city: String,
            state: {
                type: ShippingState,
                required: function () {
                    return this.get("country.isoCode") === "US";
                }
            },
            country: ShippingCountry,
            zipCode: String,

            email: String,
            phone: {
                type: String,
                required: false
            },
            fax: {
                type: String,
                required: false
            }
        }
    });

    Address.ADDRESS_TYPES = ADDRESS_TYPES;

    return Address;
});
<|endoftext|>


<commit_before>define(["sprd/data/SprdModel", "js/data/Entity", "sprd/entity/Address", "sprd/model/ShippingType", "underscore"], function(SprdModel, Entity, Address, ShippingType, _) {

    var Billing = Entity.inherit("sprd.model.Order.Billing", {
        defaults: {
            address: Address
        },

        schema: {
            address: Address
        }

    });

    var Shipping = Entity.inherit("sprd.model.Order.Shipping", {
        defaults: {
            address: Address,
            shippingType: null,

            order: null,
            shippingTypes: "{order.shippingTypes()}"
        },

        schema: {
            address: Address,
            shippingType: ShippingType
        },

        _commitShippingTypes: function (shippingTypes) {

            if (shippingTypes) {
                var shippingType = this.$.shippingType;
                if (!shippingType || _.indexOf(shippingTypes, shippingType) === -1) {
                    // select first shipping type or shipping type not available any more
                    this.set("shippingType", shippingTypes[0]);
                }
            }
        }
    });

    return SprdModel.inherit("checkout.model.Delivery", {

        defaults: {
            billing: Billing,
            shipping: Shipping,

            invoiceToShippingAddress: true
        },

        $isDependentObject: true,

        schema: {
            shipping: Shipping,
            billing: Billing,

            email: String,
            presentMessage: String
        },

        invoiceAddress: function () {
            return this.$.invoiceToShippingAddress ? this.get("shipping.address") : this.get("billing.address");
        }.onChange("invoiceToShippingAddress")

    });

});<commit_msg>added defaults to the Delivery<commit_after>define(["sprd/data/SprdModel", "js/data/Entity", "sprd/entity/Address", "sprd/model/ShippingType", "underscore"], function(SprdModel, Entity, Address, ShippingType, _) {

    var Billing = Entity.inherit("sprd.model.Order.Billing", {
        defaults: {
            address: Address
        },

        schema: {
            address: Address
        }

    });

    var Shipping = Entity.inherit("sprd.model.Order.Shipping", {
        defaults: {
            address: Address,
            shippingType: null,

            order: null,
            shippingTypes: "{order.shippingTypes()}"
        },

        schema: {
            address: Address,
            shippingType: ShippingType
        },

        _commitShippingTypes: function (shippingTypes) {

            if (shippingTypes) {
                var shippingType = this.$.shippingType;
                if (!shippingType || _.indexOf(shippingTypes, shippingType) === -1) {
                    // select first shipping type or shipping type not available any more
                    this.set("shippingType", shippingTypes[0]);
                }
            }
        }
    });

    return SprdModel.inherit("checkout.model.Delivery", {

        defaults: {
            billing: Billing,
            shipping: Shipping,

            email: null,
            presentMessage: null,
            invoiceToShippingAddress: true
        },

        $isDependentObject: true,

        schema: {
            shipping: Shipping,
            billing: Billing,

            email: String,
            presentMessage: String
        },

        invoiceAddress: function () {
            return this.$.invoiceToShippingAddress ? this.get("shipping.address") : this.get("billing.address");
        }.onChange("invoiceToShippingAddress")

    });

});<|endoftext|>


<commit_before>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import LogManager from '../fb-stubs/Logger';
import {RecurringError} from './errors';
const fs = require('fs');
const adb = require('adbkit-fb');
import {
  openssl,
  isInstalled as opensslInstalled,
} from './openssl-wrapper-with-promises';
const path = require('path');

// Desktop file paths
const os = require('os');
const caKey = getFilePath('ca.key');
const caCert = getFilePath('ca.crt');
const serverKey = getFilePath('server.key');
const serverCsr = getFilePath('server.csr');
const serverCert = getFilePath('server.crt');

// Device file paths
const csrFileName = 'app.csr';
const deviceCAcertFile = 'sonarCA.crt';
const deviceClientCertFile = 'device.crt';

const caSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=SonarCA';
const serverSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=localhost';
const minCertExpiryWindowSeconds = 24 * 60 * 60;
const appNotDebuggableRegex = /debuggable/;
const allowedAppNameRegex = /^[a-zA-Z0-9.\-]+$/;
const allowedAppDirectoryRegex = /^\/[ a-zA-Z0-9.\-\/]+$/;
const operationNotPermittedRegex = /not permitted/;
const logTag = 'CertificateProvider';
/*
 * RFC2253 specifies the unamiguous x509 subject format.
 * However, even when specifying this, different openssl implementations
 * wrap it differently, e.g "subject=X" vs "subject= X".
 */
const x509SubjectCNRegex = /[=,]\s*CN=([^,]*)(,.*)?$/;

export type SecureServerConfig = {|
  key: Buffer,
  cert: Buffer,
  ca: Buffer,
  requestCert: boolean,
  rejectUnauthorized: boolean,
|};

/*
 * This class is responsible for generating and deploying server and client
 * certificates to allow for secure communication between sonar and apps.
 * It takes a Certificate Signing Request which was generated by the app,
 * using the app's public/private keypair.
 * With this CSR it uses the sonar CA to sign a client certificate which it
 * deploys securely to the app.
 * It also deploys the sonar CA cert to the app.
 * The app can trust a server if and only if it has a certificate signed by the
 * sonar CA.
*/
export default class CertificateProvider {
  logger: LogManager;
  adb: any;
  certificateSetup: Promise<void>;
  server: Server;

  constructor(server: Server, logger: LogManager) {
    this.logger = logger;
    this.adb = adb.createClient();
    this.certificateSetup = this.ensureServerCertExists();
    this.server = server;
  }

  processCertificateSigningRequest(
    csr: string,
    os: string,
    appDirectory: string,
  ): Promise<void> {
    this.ensureOpenSSLIsAvailable();
    if (!appDirectory.match(allowedAppDirectoryRegex)) {
      return Promise.reject(
        new RecurringError(
          `Invalid appDirectory recieved from ${os} device: ${appDirectory}`,
        ),
      );
    }
    return this.certificateSetup
      .then(_ => this.getCACertificate())
      .then(caCert =>
        this.deployFileToMobileApp(
          appDirectory,
          deviceCAcertFile,
          caCert,
          csr,
          os,
        ),
      )
      .then(_ => this.generateClientCertificate(csr))
      .then(clientCert =>
        this.deployFileToMobileApp(
          appDirectory,
          deviceClientCertFile,
          clientCert,
          csr,
          os,
        ),
      )
      .catch(e => console.error(e));
  }

  ensureOpenSSLIsAvailable(): void {
    if (!opensslInstalled()) {
      const e = Error(
        "It looks like you don't have OpenSSL installed. Please install it to continue.",
      );
      this.server.emit('error', e);
    }
  }

  getCACertificate(): Promise<string> {
    return new Promise((resolve, reject) => {
      fs.readFile(caCert, (err, data) => {
        if (err) {
          reject(err);
        } else {
          resolve(data.toString());
        }
      });
    });
  }

  generateClientCertificate(csr: string): Promise<string> {
    console.warn('Creating new client cert', logTag);
    const csrFile = this.writeToTempFile(csr);
    // Create a certificate for the client, using the details in the CSR.
    return openssl('x509', {
      req: true,
      in: csrFile,
      CA: caCert,
      CAkey: caKey,
      CAcreateserial: true,
    }).then(cert => {
      fs.unlinkSync(csrFile);
      return cert;
    });
  }

  deployFileToMobileApp(
    destination: string,
    filename: string,
    contents: string,
    csr: string,
    os: string,
  ): Promise<void> {
    if (os === 'Android') {
      const appNamePromise = this.extractAppNameFromCSR(csr);
      const deviceIdPromise = appNamePromise.then(app =>
        this.getTargetDeviceId(app, destination, csr),
      );
      return Promise.all([deviceIdPromise, appNamePromise]).then(
        ([deviceId, appName]) =>
          this.pushFileToAndroidDevice(
            deviceId,
            appName,
            destination + filename,
            contents,
          ),
      );
    }
    if (os === 'iOS' || os === 'windows') {
      fs.writeFileSync(destination + filename, contents);
      return Promise.resolve();
    }
    return Promise.reject(new RecurringError(`Unsupported device os: ${os}`));
  }

  getTargetDeviceId(
    appName: string,
    deviceCsrFilePath: string,
    csr: string,
  ): Promise<string> {
    const client = adb.createClient();
    return client.listDevices().then((devices: Array<{id: string}>) => {
      const deviceMatchList = devices.map(device =>
        // To find out which device requested the cert, search them
        // all for a matching csr file.
        // It's not important to keep these secret from other apps.
        // Just need to make sure each app can find it's own one.
        this.androidDeviceHasMatchingCSR(
          deviceCsrFilePath,
          device.id,
          appName,
          csr,
        )
          .then(isMatch => {
            return {id: device.id, isMatch};
          })
          .catch(e => {
            console.error(
              `Unable to check for matching CSR in ${device.id}:${appName}`,
              logTag,
            );
            return {id: device.id, isMatch: false};
          }),
      );
      return Promise.all(deviceMatchList).then(devices => {
        const matchingIds = devices.filter(m => m.isMatch).map(m => m.id);
        if (matchingIds.length == 0) {
          throw new RecurringError(
            `No matching device found for app: ${appName}`,
          );
        }
        return matchingIds[0];
      });
    });
  }

  androidDeviceHasMatchingCSR(
    directory: string,
    deviceId: string,
    processName: string,
    csr: string,
  ): Promise<boolean> {
    return this.executeCommandOnAndroid(
      deviceId,
      processName,
      `cat ${directory + csrFileName}`,
    )
      .then(deviceCsr => {
        return (
          deviceCsr
            .toString()
            .replace(/\r/g, '')
            .trim() === csr.replace(/\r/g, '').trim()
        );
      })
      .catch(err => {
        console.error(err, logTag);
        return false;
      });
  }

  pushFileToAndroidDevice(
    deviceId: string,
    app: string,
    filename: string,
    contents: string,
  ): Promise<void> {
    console.warn(`Deploying ${filename} to ${deviceId}:${app}`, logTag);
    return this.executeCommandOnAndroid(
      deviceId,
      app,
      `echo "${contents}" > ${filename} && chmod 600 ${filename}`,
    ).then(output => undefined);
  }

  executeCommandOnAndroid(
    deviceId: string,
    user: string,
    command: string,
  ): Promise<string> {
    if (!user.match(allowedAppNameRegex)) {
      return Promise.reject(
        new RecurringError(`Disallowed run-as user: ${user}`),
      );
    }
    if (command.match(/[']/)) {
      return Promise.reject(
        new RecurringError(`Disallowed escaping command: ${command}`),
      );
    }
    return this.adb
      .shell(deviceId, `echo '${command}' | run-as '${user}'`)
      .then(adb.util.readAll)
      .then(buffer => buffer.toString())
      .then(output => {
        if (output.match(appNotDebuggableRegex)) {
          const e = new RecurringError(
            `Android app ${user} is not debuggable. To use it with sonar, add android:debuggable="true" to the application section of AndroidManifest.xml`,
          );
          this.server.emit('error', e);
          throw e;
        }
        if (output.toLowerCase().match(operationNotPermittedRegex)) {
          const e = new RecurringError(
            `Your android device (${deviceId}) does not support the adb shell run-as command. We're tracking this at https://github.com/facebook/Sonar/issues/92`,
          );
          this.server.emit('error', e);
          throw e;
        }
        return output;
      });
  }

  extractAppNameFromCSR(csr: string): Promise<string> {
    const csrFile = this.writeToTempFile(csr);
    return openssl('req', {
      in: csrFile,
      noout: true,
      subject: true,
      nameopt: true,
      RFC2253: false,
    })
      .then(subject => {
        fs.unlink(csrFile);
        return subject;
      })
      .then(subject => {
        const matches = subject.trim().match(x509SubjectCNRegex);
        if (!matches || matches.length < 2) {
          throw new RecurringError(`Cannot extract CN from ${subject}`);
        }
        return matches[1];
      })
      .then(appName => {
        if (!appName.match(allowedAppNameRegex)) {
          throw new RecurringError(
            `Disallowed app name in CSR: ${appName}. Only alphanumeric characters and '.' allowed.`,
          );
        }
        return appName;
      });
  }

  loadSecureServerConfig(): Promise<SecureServerConfig> {
    return this.certificateSetup.then(() => {
      return {
        key: fs.readFileSync(serverKey),
        cert: fs.readFileSync(serverCert),
        ca: fs.readFileSync(caCert),
        requestCert: true,
        rejectUnauthorized: true, // can be false if necessary as we don't strictly need to verify the client
      };
    });
  }

  ensureCertificateAuthorityExists(): Promise<void> {
    if (!fs.existsSync(caKey)) {
      return this.generateCertificateAuthority();
    }
    return this.checkCertIsValid(caCert).catch(e =>
      this.generateCertificateAuthority(),
    );
  }

  checkCertIsValid(filename: string): Promise<void> {
    if (!fs.existsSync(filename)) {
      return Promise.reject();
    }
    // openssl checkend is a nice feature but it only checks for certificates
    // expiring in the future, not those that have already expired.
    // So we need a separate check for certificates that have already expired
    // but since this involves parsing date outputs from openssl, which is less
    // reliable, keeping both checks for safety.
    return openssl('x509', {
      checkend: minCertExpiryWindowSeconds,
      in: filename,
    })
      .then(output => undefined)
      .catch(e => {
        console.warn(`Certificate will expire soon: ${filename}`, logTag);
        throw e;
      })
      .then(_ =>
        openssl('x509', {
          enddate: true,
          in: filename,
          noout: true,
        }),
      )
      .then(endDateOutput => {
        const dateString = endDateOutput
          .trim()
          .split('=')[1]
          .trim();
        const expiryDate = Date.parse(dateString);
        if (isNaN(expiryDate)) {
          console.error(
            'Unable to parse certificate expiry date: ' + endDateOutput,
          );
          throw new Error(
            'Cannot parse certificate expiry date. Assuming it has expired.',
          );
        }
        if (expiryDate <= Date.now() + minCertExpiryWindowSeconds * 1000) {
          throw new Error('Certificate has expired or will expire soon.');
        }
      });
  }

  verifyServerCertWasIssuedByCA() {
    const options = {CAfile: caCert};
    options[serverCert] = false;
    return openssl('verify', options).then(output => {
      const verified = output.match(/[^:]+: OK/);
      if (!verified) {
        // This should never happen, but if it does, we need to notice so we can
        // generate a valid one, or no clients will trust our server.
        throw new Error('Current server cert was not issued by current CA');
      }
    });
  }

  generateCertificateAuthority(): Promise<void> {
    if (!fs.existsSync(getFilePath(''))) {
      fs.mkdirSync(getFilePath(''));
    }
    console.log('Generating new CA', logTag);
    return openssl('genrsa', {out: caKey, '2048': false})
      .then(_ =>
        openssl('req', {
          new: true,
          x509: true,
          subj: caSubject,
          key: caKey,
          out: caCert,
        }),
      )
      .then(_ => undefined);
  }

  ensureServerCertExists(): Promise<void> {
    if (
      !(
        fs.existsSync(serverKey) &&
        fs.existsSync(serverCert) &&
        fs.existsSync(caCert)
      )
    ) {
      return this.generateServerCertificate();
    }

    return this.checkCertIsValid(serverCert)
      .then(_ => this.verifyServerCertWasIssuedByCA())
      .catch(e => this.generateServerCertificate());
  }

  generateServerCertificate(): Promise<void> {
    return this.ensureCertificateAuthorityExists()
      .then(_ => {
        console.warn('Creating new server cert', logTag);
      })
      .then(_ => openssl('genrsa', {out: serverKey, '2048': false}))
      .then(_ =>
        openssl('req', {
          new: true,
          key: serverKey,
          out: serverCsr,
          subj: serverSubject,
        }),
      )
      .then(_ =>
        openssl('x509', {
          req: true,
          in: serverCsr,
          CA: caCert,
          CAkey: caKey,
          CAcreateserial: true,
          out: serverCert,
        }),
      )
      .then(_ => undefined);
  }

  writeToTempFile(content: string): string {
    const fileName = getFilePath(`deviceCSR-${Math.random() * 1000000}`);
    fs.writeFileSync(fileName, content);
    return fileName;
  }
}

function getFilePath(fileName: string): string {
  return path.resolve(os.homedir(), '.sonar', 'certs', fileName);
}
<commit_msg>Allow filesystem to decide path validity for cert dest folder (sonarCA.cert failing to write on windows)<commit_after>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */

import LogManager from '../fb-stubs/Logger';
import {RecurringError} from './errors';
const fs = require('fs');
const adb = require('adbkit-fb');
import {
  openssl,
  isInstalled as opensslInstalled,
} from './openssl-wrapper-with-promises';
const path = require('path');

// Desktop file paths
const os = require('os');
const caKey = getFilePath('ca.key');
const caCert = getFilePath('ca.crt');
const serverKey = getFilePath('server.key');
const serverCsr = getFilePath('server.csr');
const serverCert = getFilePath('server.crt');

// Device file paths
const csrFileName = 'app.csr';
const deviceCAcertFile = 'sonarCA.crt';
const deviceClientCertFile = 'device.crt';

const caSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=SonarCA';
const serverSubject = '/C=US/ST=CA/L=Menlo Park/O=Sonar/CN=localhost';
const minCertExpiryWindowSeconds = 24 * 60 * 60;
const appNotDebuggableRegex = /debuggable/;
const allowedAppNameRegex = /^[a-zA-Z0-9.\-]+$/;
const operationNotPermittedRegex = /not permitted/;
const logTag = 'CertificateProvider';
/*
 * RFC2253 specifies the unamiguous x509 subject format.
 * However, even when specifying this, different openssl implementations
 * wrap it differently, e.g "subject=X" vs "subject= X".
 */
const x509SubjectCNRegex = /[=,]\s*CN=([^,]*)(,.*)?$/;

export type SecureServerConfig = {|
  key: Buffer,
  cert: Buffer,
  ca: Buffer,
  requestCert: boolean,
  rejectUnauthorized: boolean,
|};

/*
 * This class is responsible for generating and deploying server and client
 * certificates to allow for secure communication between sonar and apps.
 * It takes a Certificate Signing Request which was generated by the app,
 * using the app's public/private keypair.
 * With this CSR it uses the sonar CA to sign a client certificate which it
 * deploys securely to the app.
 * It also deploys the sonar CA cert to the app.
 * The app can trust a server if and only if it has a certificate signed by the
 * sonar CA.
*/
export default class CertificateProvider {
  logger: LogManager;
  adb: any;
  certificateSetup: Promise<void>;
  server: Server;

  constructor(server: Server, logger: LogManager) {
    this.logger = logger;
    this.adb = adb.createClient();
    this.certificateSetup = this.ensureServerCertExists();
    this.server = server;
  }

  processCertificateSigningRequest(
    csr: string,
    os: string,
    appDirectory: string,
  ): Promise<void> {
    this.ensureOpenSSLIsAvailable();
    return this.certificateSetup
      .then(_ => this.getCACertificate())
      .then(caCert =>
        this.deployFileToMobileApp(
          appDirectory,
          deviceCAcertFile,
          caCert,
          csr,
          os,
        ),
      )
      .then(_ => this.generateClientCertificate(csr))
      .then(clientCert =>
        this.deployFileToMobileApp(
          appDirectory,
          deviceClientCertFile,
          clientCert,
          csr,
          os,
        ),
      )
      .catch(e => console.error(e));
  }

  ensureOpenSSLIsAvailable(): void {
    if (!opensslInstalled()) {
      const e = Error(
        "It looks like you don't have OpenSSL installed. Please install it to continue.",
      );
      this.server.emit('error', e);
    }
  }

  getCACertificate(): Promise<string> {
    return new Promise((resolve, reject) => {
      fs.readFile(caCert, (err, data) => {
        if (err) {
          reject(err);
        } else {
          resolve(data.toString());
        }
      });
    });
  }

  generateClientCertificate(csr: string): Promise<string> {
    console.warn('Creating new client cert', logTag);
    const csrFile = this.writeToTempFile(csr);
    // Create a certificate for the client, using the details in the CSR.
    return openssl('x509', {
      req: true,
      in: csrFile,
      CA: caCert,
      CAkey: caKey,
      CAcreateserial: true,
    }).then(cert => {
      fs.unlinkSync(csrFile);
      return cert;
    });
  }

  deployFileToMobileApp(
    destination: string,
    filename: string,
    contents: string,
    csr: string,
    os: string,
  ): Promise<void> {
    if (os === 'Android') {
      const appNamePromise = this.extractAppNameFromCSR(csr);
      const deviceIdPromise = appNamePromise.then(app =>
        this.getTargetDeviceId(app, destination, csr),
      );
      return Promise.all([deviceIdPromise, appNamePromise]).then(
        ([deviceId, appName]) =>
          this.pushFileToAndroidDevice(
            deviceId,
            appName,
            destination + filename,
            contents,
          ),
      );
    }
    if (os === 'iOS' || os === 'windows') {
      return new Promise((resolve, reject) => {
        fs.writeFile(destination + filename, contents, err => {
          if (err) {
            reject(
              `Invalid appDirectory recieved from ${os} device: ${destination}: ` +
                err.toString(),
            );
          } else {
            resolve();
          }
        });
      });
    }
    return Promise.reject(new RecurringError(`Unsupported device os: ${os}`));
  }

  getTargetDeviceId(
    appName: string,
    deviceCsrFilePath: string,
    csr: string,
  ): Promise<string> {
    const client = adb.createClient();
    return client.listDevices().then((devices: Array<{id: string}>) => {
      const deviceMatchList = devices.map(device =>
        // To find out which device requested the cert, search them
        // all for a matching csr file.
        // It's not important to keep these secret from other apps.
        // Just need to make sure each app can find it's own one.
        this.androidDeviceHasMatchingCSR(
          deviceCsrFilePath,
          device.id,
          appName,
          csr,
        )
          .then(isMatch => {
            return {id: device.id, isMatch};
          })
          .catch(e => {
            console.error(
              `Unable to check for matching CSR in ${device.id}:${appName}`,
              logTag,
            );
            return {id: device.id, isMatch: false};
          }),
      );
      return Promise.all(deviceMatchList).then(devices => {
        const matchingIds = devices.filter(m => m.isMatch).map(m => m.id);
        if (matchingIds.length == 0) {
          throw new RecurringError(
            `No matching device found for app: ${appName}`,
          );
        }
        return matchingIds[0];
      });
    });
  }

  androidDeviceHasMatchingCSR(
    directory: string,
    deviceId: string,
    processName: string,
    csr: string,
  ): Promise<boolean> {
    return this.executeCommandOnAndroid(
      deviceId,
      processName,
      `cat ${directory + csrFileName}`,
    )
      .then(deviceCsr => {
        return (
          deviceCsr
            .toString()
            .replace(/\r/g, '')
            .trim() === csr.replace(/\r/g, '').trim()
        );
      })
      .catch(err => {
        console.error(err, logTag);
        return false;
      });
  }

  pushFileToAndroidDevice(
    deviceId: string,
    app: string,
    filename: string,
    contents: string,
  ): Promise<void> {
    console.warn(`Deploying ${filename} to ${deviceId}:${app}`, logTag);
    return this.executeCommandOnAndroid(
      deviceId,
      app,
      `echo "${contents}" > ${filename} && chmod 600 ${filename}`,
    ).then(output => undefined);
  }

  executeCommandOnAndroid(
    deviceId: string,
    user: string,
    command: string,
  ): Promise<string> {
    if (!user.match(allowedAppNameRegex)) {
      return Promise.reject(
        new RecurringError(`Disallowed run-as user: ${user}`),
      );
    }
    if (command.match(/[']/)) {
      return Promise.reject(
        new RecurringError(`Disallowed escaping command: ${command}`),
      );
    }
    return this.adb
      .shell(deviceId, `echo '${command}' | run-as '${user}'`)
      .then(adb.util.readAll)
      .then(buffer => buffer.toString())
      .then(output => {
        if (output.match(appNotDebuggableRegex)) {
          const e = new RecurringError(
            `Android app ${user} is not debuggable. To use it with sonar, add android:debuggable="true" to the application section of AndroidManifest.xml`,
          );
          this.server.emit('error', e);
          throw e;
        }
        if (output.toLowerCase().match(operationNotPermittedRegex)) {
          const e = new RecurringError(
            `Your android device (${deviceId}) does not support the adb shell run-as command. We're tracking this at https://github.com/facebook/Sonar/issues/92`,
          );
          this.server.emit('error', e);
          throw e;
        }
        return output;
      });
  }

  extractAppNameFromCSR(csr: string): Promise<string> {
    const csrFile = this.writeToTempFile(csr);
    return openssl('req', {
      in: csrFile,
      noout: true,
      subject: true,
      nameopt: true,
      RFC2253: false,
    })
      .then(subject => {
        fs.unlink(csrFile);
        return subject;
      })
      .then(subject => {
        const matches = subject.trim().match(x509SubjectCNRegex);
        if (!matches || matches.length < 2) {
          throw new RecurringError(`Cannot extract CN from ${subject}`);
        }
        return matches[1];
      })
      .then(appName => {
        if (!appName.match(allowedAppNameRegex)) {
          throw new RecurringError(
            `Disallowed app name in CSR: ${appName}. Only alphanumeric characters and '.' allowed.`,
          );
        }
        return appName;
      });
  }

  loadSecureServerConfig(): Promise<SecureServerConfig> {
    return this.certificateSetup.then(() => {
      return {
        key: fs.readFileSync(serverKey),
        cert: fs.readFileSync(serverCert),
        ca: fs.readFileSync(caCert),
        requestCert: true,
        rejectUnauthorized: true, // can be false if necessary as we don't strictly need to verify the client
      };
    });
  }

  ensureCertificateAuthorityExists(): Promise<void> {
    if (!fs.existsSync(caKey)) {
      return this.generateCertificateAuthority();
    }
    return this.checkCertIsValid(caCert).catch(e =>
      this.generateCertificateAuthority(),
    );
  }

  checkCertIsValid(filename: string): Promise<void> {
    if (!fs.existsSync(filename)) {
      return Promise.reject();
    }
    // openssl checkend is a nice feature but it only checks for certificates
    // expiring in the future, not those that have already expired.
    // So we need a separate check for certificates that have already expired
    // but since this involves parsing date outputs from openssl, which is less
    // reliable, keeping both checks for safety.
    return openssl('x509', {
      checkend: minCertExpiryWindowSeconds,
      in: filename,
    })
      .then(output => undefined)
      .catch(e => {
        console.warn(`Certificate will expire soon: ${filename}`, logTag);
        throw e;
      })
      .then(_ =>
        openssl('x509', {
          enddate: true,
          in: filename,
          noout: true,
        }),
      )
      .then(endDateOutput => {
        const dateString = endDateOutput
          .trim()
          .split('=')[1]
          .trim();
        const expiryDate = Date.parse(dateString);
        if (isNaN(expiryDate)) {
          console.error(
            'Unable to parse certificate expiry date: ' + endDateOutput,
          );
          throw new Error(
            'Cannot parse certificate expiry date. Assuming it has expired.',
          );
        }
        if (expiryDate <= Date.now() + minCertExpiryWindowSeconds * 1000) {
          throw new Error('Certificate has expired or will expire soon.');
        }
      });
  }

  verifyServerCertWasIssuedByCA() {
    const options = {CAfile: caCert};
    options[serverCert] = false;
    return openssl('verify', options).then(output => {
      const verified = output.match(/[^:]+: OK/);
      if (!verified) {
        // This should never happen, but if it does, we need to notice so we can
        // generate a valid one, or no clients will trust our server.
        throw new Error('Current server cert was not issued by current CA');
      }
    });
  }

  generateCertificateAuthority(): Promise<void> {
    if (!fs.existsSync(getFilePath(''))) {
      fs.mkdirSync(getFilePath(''));
    }
    console.log('Generating new CA', logTag);
    return openssl('genrsa', {out: caKey, '2048': false})
      .then(_ =>
        openssl('req', {
          new: true,
          x509: true,
          subj: caSubject,
          key: caKey,
          out: caCert,
        }),
      )
      .then(_ => undefined);
  }

  ensureServerCertExists(): Promise<void> {
    if (
      !(
        fs.existsSync(serverKey) &&
        fs.existsSync(serverCert) &&
        fs.existsSync(caCert)
      )
    ) {
      return this.generateServerCertificate();
    }

    return this.checkCertIsValid(serverCert)
      .then(_ => this.verifyServerCertWasIssuedByCA())
      .catch(e => this.generateServerCertificate());
  }

  generateServerCertificate(): Promise<void> {
    return this.ensureCertificateAuthorityExists()
      .then(_ => {
        console.warn('Creating new server cert', logTag);
      })
      .then(_ => openssl('genrsa', {out: serverKey, '2048': false}))
      .then(_ =>
        openssl('req', {
          new: true,
          key: serverKey,
          out: serverCsr,
          subj: serverSubject,
        }),
      )
      .then(_ =>
        openssl('x509', {
          req: true,
          in: serverCsr,
          CA: caCert,
          CAkey: caKey,
          CAcreateserial: true,
          out: serverCert,
        }),
      )
      .then(_ => undefined);
  }

  writeToTempFile(content: string): string {
    const fileName = getFilePath(`deviceCSR-${Math.random() * 1000000}`);
    fs.writeFileSync(fileName, content);
    return fileName;
  }
}

function getFilePath(fileName: string): string {
  return path.resolve(os.homedir(), '.sonar', 'certs', fileName);
}
<|endoftext|>


<commit_before>/*
MIT License

Copyright (c) 2016 Grall Arnaud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
'use strict';

/**
 * A Middleware registry coordintaes middleware in a foglet application
 * @author Thomas Minier
 */
class MiddlewareRegistry {
  /**
   * Constructor
   */
  constructor () {
    this._middlewares = [];
  }

  /**
   * Register a middleware, with an optional priority
   * @param  {Object} middleware   - The middleware to register
   * @param  {function} middleware.in - Function applied on middleware input
   * @param  {function} middleware.out - Function applied on middleware output
   * @param  {Number} [priority=0] - (optional) The middleware priority
   * @return {void}
   */
  register (middleware, priority = 0) {
    if (!('in' in middleware) && !('out' in middleware))
      throw new Error('A middleware must contains two functions: "in" and "out"');
    this._middlewares.push({
      middleware,
      priority
    });
    this._middlewares.sort((x, y) => x.priority - y.priority);
  }

  /**
   * Apply middleware on input data
   * @param  {*} data - Input data
   * @return {*} Input data transformed by successive application of middlewares
   */
  in (data) {
    let temp;
    return this._middlewares.reduce((input, obj) => {
      temp = obj.middleware.in(input);
      if (temp !== undefined || temp !== null)
        return temp;
      return input;
    }, data);
  }

  /**
   * Apply middleware on output data
   * @param  {*} data - Output data
   * @return {*} Output data transformed by successive application of middlewares
   */
  out (data) {
    let temp;
    return this._middlewares.reduce((input, obj) => {
      temp = obj.middleware.in(input);
      if (temp !== undefined || temp !== null)
        return temp;
      return input;
    }, data);
  }
}

module.exports = MiddlewareRegistry;
<commit_msg>fix bugs in middleware<commit_after>/*
MIT License

Copyright (c) 2016 Grall Arnaud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
'use strict';

/**
 * A Middleware registry coordintaes middleware in a foglet application
 * @author Thomas Minier
 */
class MiddlewareRegistry {
  /**
   * Constructor
   */
  constructor () {
    this._middlewares = [];
  }

  /**
   * Register a middleware, with an optional priority
   * @param  {Object} middleware   - The middleware to register
   * @param  {function} middleware.in - Function applied on middleware input
   * @param  {function} middleware.out - Function applied on middleware output
   * @param  {Number} [priority=0] - (optional) The middleware priority
   * @return {void}
   */
  register (middleware, priority = 0) {
    if (!('in' in middleware) && !('out' in middleware))
      throw new Error('A middleware must contains two functions: "in" and "out"');
    this._middlewares.push({
      middleware,
      priority
    });
    this._middlewares.sort((x, y) => x.priority - y.priority);
  }

  /**
   * Apply middleware on input data
   * @param  {*} data - Input data
   * @return {*} Input data transformed by successive application of middlewares
   */
  in (data) {
    let temp;
    return this._middlewares.reduce((input, obj) => {
      temp = obj.middleware.in(input);
      if (temp !== undefined || temp !== null)
        return temp;
      return input;
    }, data);
  }

  /**
   * Apply middleware on output data
   * @param  {*} data - Output data
   * @return {*} Output data transformed by successive application of middlewares
   */
  out (data) {
    let temp;
    return this._middlewares.reduce((input, obj) => {
      temp = obj.middleware.out(input);
      if (temp !== undefined || temp !== null)
        return temp;
      return input;
    }, data);
  }
}

module.exports = MiddlewareRegistry;
<|endoftext|>