<commit_before>// Copyright 2014-2015, Renasar Technologies Inc.
/* jshint node: true */

'use strict';

var di = require('di'),
    fs = require('fs'),
    path = require('path');

module.exports = ChildProcessFactory;

di.annotate(ChildProcessFactory, new di.Provide('ChildProcess'));
di.annotate(ChildProcessFactory,
    new di.Inject(
        'child_process',
        'Errors',
        'Logger',
        'Assert',
        'util',
        'Q',
        '_'
    )
);

/**
 * childProcessFactory returns a ChildProcess constructor
 * @param {Q} Q Promise Library
 * @param {Logger} Logger module
 * @param {Object} assert assertion module
 * @param {Object} _ lodash module
 * @private
 */
function ChildProcessFactory (nodeChildProcess, BaseError, Logger, assert, util, Q, _) {
    var logger = Logger.initialize(ChildProcessFactory);

    function JobKilledError(message) {
        JobKilledError.super_.call(this, message);
        Error.captureStackTrace(this, JobKilledError);
    }
    util.inherits(JobKilledError, BaseError);

    /**
     * ChildProcess provides a promise based mechanism to run shell commands
     * in a fairly secure manner.
     * @constructor
     */
    function ChildProcess (command, args, env, code) {
        var self = this;

        self.command = command;
        self.file = self._parseCommandPath(self.command);
        self.args = args;
        self.environment = env || {};
        self.exitCode = code || 0;

        if (!self.file) {
            throw new Error('Unable to locate command file (' + self.command +').');
        }
        if (!_.isEmpty(self.args)) {
            assert.arrayOfString(self.args, 'ChildProcess command arguments');
        }

        self.hasBeenKilled = false;
        self.hasRun = false;
        self.spawnInstance = undefined;
        self.done = false;

        self._deferred = Q.defer();
        self._deferred.promise.finally(function() {
            self.done = true;
        });
    }

    ChildProcess.prototype.resolve = function() {
        if (this.done) {
            logger.warning("ChildProcess promise has already been resolved");
            return;
        }
        this._deferred.resolve();
    };

    ChildProcess.prototype.reject = function(error) {
        if (this.done) {
            logger.warning("ChildProcess promise has already been rejected");
            return;
        }
        this._deferred.reject(error);
    };

    ChildProcess.prototype.killSafe = function (signal) {
        if (!this.hasBeenKilled && this.spawnInstance && _.isFunction(this.spawnInstance.kill)) {
            this.spawnInstance.kill(signal);
        } else {
            this.hasBeenKilled = true;
        }
    };

    /**
     * Runs the given command.
     * @param  {String} command File to run, with path or without.
     * @param  {String[]} args    Arguments to the file.
     * @param  {Object} env     Optional environment variables to provide.
     * @param  {Integer} code   Desired exit code, defaults to 0.
     * @return {Q.Promise}        A promise fulfilled with the stdout, stderr of
     * a successful command.
     */
    ChildProcess.prototype._run = function () {
        var self = this;

        if (self.hasBeenKilled) {
            self.reject(new JobKilledError("ChildProcess job has been killed", {
                command: self.command,
                argv: self.args
            }));
            return;
        }

        self.spawnInstance = nodeChildProcess.execFile(
                self.file, self.args, self.environment, function (error, stdout, stderr) {

            if (error && error.code !== self.exitCode) {
                self.hasBeenKilled = true;
                logger.error('Error Running ChildProcess.', {
                    file: self.file,
                    argv: self.args,
                    stdout: stdout,
                    stderr: stderr,
                    error: error
                });

                this.reject(error);
            } else {
                self.hasBeenKilled = true;
                self.resolve({
                    stdout: stdout,
                    stderr: stderr
                });
            }
        })
        .on('close', function(code, signal) {
            if (signal) {
                logger.warning("Child process received closing signal:", {
                    signal: signal,
                    argv: self.args
                });
            }
            self.hasBeenKilled = true;
        })
        .on('error', function(code, signal) {
            logger.error("Child process received closing signal but has " +
                "already been closed!!!", {
                signal: signal,
                argv: self.args
            });
        });
    };

    ChildProcess.prototype.run = function(options) {
        var self = this;
        options = options || {};
        options.retries = options.retries || 1;

        function _runWithRetries(retryCount) {
            self._run();

            return self._deferred.promise.catch(function(e) {
                if (e.name === 'JobKilledError') {
                    throw e;
                } else if (retryCount < options.retries) {
                    logger.debug("Retrying ChildProcess command.", {
                        error: e
                    });
                    self._deferred = Q.defer();
                    return _runWithRetries(retryCount + 1);
                } else {
                    throw e;
                }
            });
        }

        return _runWithRetries(0);
    };

    /**
     * Internal method to identify the path to the command file.  It's essentially
     * unix which in JavaScript.
     * @private
     */
    ChildProcess.prototype._parseCommandPath = function _parseCommandPath (command) {
        var self = this;

        if (self._fileExists(command)) {
            return command;
        } else {
            var found = _.some(self._getPaths(), function (current) {
                var target = path.resolve(current + '/' + command);

                if (self._fileExists(target)) {
                    command = target;

                    return true;
                }
            });

            return found ? command : null;
        }
    };

    /**
     * Internal method to verify a file exists and is not a directory.
     * @private
     */
    ChildProcess.prototype._fileExists = function _fileExists (file) {
        return fs.existsSync(file) && !fs.statSync(file).isDirectory();
    };

    /**
     * Internal method to get an array of directories in the users path.
     * @private
     */
    ChildProcess.prototype._getPaths = function _getPaths () {
        var path = process.env.path || process.env.Path || process.env.PATH;
        return path.split(':');
    };

    return ChildProcess;
}
<commit_msg>Minor fixes to child-process<commit_after>// Copyright 2014-2015, Renasar Technologies Inc.
/* jshint node: true */

'use strict';

var di = require('di'),
    fs = require('fs'),
    path = require('path');

module.exports = ChildProcessFactory;

di.annotate(ChildProcessFactory, new di.Provide('ChildProcess'));
di.annotate(ChildProcessFactory,
    new di.Inject(
        'child_process',
        'Errors',
        'Logger',
        'Assert',
        'Util',
        'Q',
        '_'
    )
);

/**
 * childProcessFactory returns a ChildProcess constructor
 * @param {Q} Q Promise Library
 * @param {Logger} Logger module
 * @param {Object} assert assertion module
 * @param {Object} _ lodash module
 * @private
 */
function ChildProcessFactory (nodeChildProcess, Errors, Logger, assert, util, Q, _) {
    var logger = Logger.initialize(ChildProcessFactory);

    function JobKilledError(message) {
        JobKilledError.super_.call(this, message);
        Error.captureStackTrace(this, JobKilledError);
    }
    util.inherits(JobKilledError, Errors.BaseError);

    /**
     * ChildProcess provides a promise based mechanism to run shell commands
     * in a fairly secure manner.
     * @constructor
     */
    function ChildProcess (command, args, env, code) {
        var self = this;

        self.command = command;
        self.file = self._parseCommandPath(self.command);
        self.args = args;
        self.environment = env || {};
        self.exitCode = code || 0;

        if (!self.file) {
            throw new Error('Unable to locate command file (' + self.command +').');
        }
        if (!_.isEmpty(self.args)) {
            try {
                assert.arrayOfString(self.args, 'ChildProcess command arguments');
            } catch (e) {
                throw new Error('args must be an array of strings');
            }
        }

        self.hasBeenKilled = false;
        self.spawnInstance = undefined;
        self.done = false;

        self._deferred = Q.defer();
    }

    ChildProcess.prototype.resolve = function() {
        if (this.done) {
            logger.warning("ChildProcess promise has already been resolved");
            return;
        }
        this.done = true;
        this._deferred.resolve();
    };

    ChildProcess.prototype.reject = function(error) {
        if (this.done) {
            logger.warning("ChildProcess promise has already been rejected");
            return;
        }
        this.done = true;
        this._deferred.reject(error);
    };

    ChildProcess.prototype.killSafe = function (signal) {
        if (!this.hasBeenKilled && this.spawnInstance && _.isFunction(this.spawnInstance.kill)) {
            this.spawnInstance.kill(signal);
        } else {
            this.hasBeenKilled = true;
        }
    };

    /**
     * Runs the given command.
     * @param  {String} command File to run, with path or without.
     * @param  {String[]} args    Arguments to the file.
     * @param  {Object} env     Optional environment variables to provide.
     * @param  {Integer} code   Desired exit code, defaults to 0.
     * @return {Q.Promise}        A promise fulfilled with the stdout, stderr of
     * a successful command.
     */
    ChildProcess.prototype._run = function () {
        var self = this;

        if (self.hasBeenKilled) {
            self.reject(new JobKilledError("ChildProcess job has been killed", {
                command: self.command,
                argv: self.args
            }));
            return;
        }

        self.spawnInstance = nodeChildProcess.execFile(
                self.file, self.args, self.environment, function (error, stdout, stderr) {

            if (error && error.code !== self.exitCode) {
                self.hasBeenKilled = true;
                logger.error('Error Running ChildProcess.', {
                    file: self.file,
                    argv: self.args,
                    stdout: stdout,
                    stderr: stderr,
                    error: error
                });

                self.reject(error);
            } else {
                self.hasBeenKilled = true;
                self.resolve({
                    stdout: stdout,
                    stderr: stderr
                });
            }
        })
        .on('close', function(code, signal) {
            if (signal) {
                logger.warning("Child process received closing signal:", {
                    signal: signal,
                    argv: self.args
                });
            }
            self.hasBeenKilled = true;
        })
        .on('error', function(code, signal) {
            logger.error("Child process received closing signal but has " +
                "already been closed!!!", {
                signal: signal,
                argv: self.args
            });
        });
    };

    ChildProcess.prototype.run = function(options) {
        var self = this;
        options = options || {};
        options.retries = options.retries || 0;

        function _runWithRetries(retryCount) {
            self._run();

            return self._deferred.promise.catch(function(e) {
                if (e.name === 'JobKilledError') {
                    throw e;
                } else if (retryCount < options.retries) {
                    logger.debug("Retrying ChildProcess command.", {
                        error: e
                    });
                    self._deferred = Q.defer();
                    return _runWithRetries(retryCount + 1);
                } else {
                    throw e;
                }
            });
        }

        return _runWithRetries(0);
    };

    /**
     * Internal method to identify the path to the command file.  It's essentially
     * unix which in JavaScript.
     * @private
     */
    ChildProcess.prototype._parseCommandPath = function _parseCommandPath (command) {
        var self = this;

        if (self._fileExists(command)) {
            return command;
        } else {
            var found = _.some(self._getPaths(), function (current) {
                var target = path.resolve(current + '/' + command);

                if (self._fileExists(target)) {
                    command = target;

                    return true;
                }
            });

            return found ? command : null;
        }
    };

    /**
     * Internal method to verify a file exists and is not a directory.
     * @private
     */
    ChildProcess.prototype._fileExists = function _fileExists (file) {
        return fs.existsSync(file) && !fs.statSync(file).isDirectory();
    };

    /**
     * Internal method to get an array of directories in the users path.
     * @private
     */
    ChildProcess.prototype._getPaths = function _getPaths () {
        var path = process.env.path || process.env.Path || process.env.PATH;
        return path.split(':');
    };

    ChildProcess.JobKilledError = JobKilledError;

    return ChildProcess;
}
<|endoftext|>


<commit_before>// This is the match pattern for tokens
_matchToken = Match.OneOf({ apn: String }, { gcm: String });

// Notifications collection
Push.notifications = new Mongo.Collection('_raix_push_notifications');

// This is a general function to validate that the data added to notifications
// is in the correct format. If not this function will throw errors
var _validateDocument = function(notification) {

  // Check the general notification
  check(notification, {
    from: String,
    title: String,
    text: String,
    sent: Match.Optional(Boolean),
    sending: Match.Optional(Match.Integer),
    badge: Match.Optional(Match.Integer),
    sound: Match.Optional(String),
    notId: Match.Optional(Match.Integer),
    contentAvailable: Match.Optional(Match.Integer),
    apn: Match.Optional({
      from: Match.Optional(String),
      title: Match.Optional(String),
      text: Match.Optional(String),
      badge: Match.Optional(Match.Integer),
      sound: Match.Optional(String),
      notId: Match.Optional(Match.Integer)
    }),
    gcm: Match.Optional({
      from: Match.Optional(String),
      title: Match.Optional(String),
      text: Match.Optional(String),
      image: Match.Optional(String),
      style: Match.Optional(String),
      summaryText: Match.Optional(String),
      picture: Match.Optional(String),
      badge: Match.Optional(Match.Integer),
      sound: Match.Optional(String),
      notId: Match.Optional(Match.Integer)
    }),
    query: Match.Optional(String),
    token: Match.Optional(_matchToken),
    tokens: Match.Optional([_matchToken]),
    payload: Match.Optional(Object),
    delayUntil: Match.Optional(Date),
    createdAt: Date,
    createdBy: Match.OneOf(String, null)
  });

  // Make sure a token selector or query have been set
  if (!notification.token && !notification.tokens && !notification.query) {
    throw new Error('No token selector or query found');
  }

  // If tokens array is set it should not be empty
  if (notification.tokens && !notification.tokens.length) {
    throw new Error('No tokens in array');
  }
};

Push.send = function(options) {
  // If on the client we set the user id - on the server we need an option
  // set or we default to "<SERVER>" as the creator of the notification
  // If current user not set see if we can set it to the logged in user
  // this will only run on the client if Meteor.userId is available
  var currentUser = Meteor.isClient && Meteor.userId && Meteor.userId() ||
          Meteor.isServer && (options.createdBy || '<SERVER>') || null;

  // Rig the notification object
   var notification = _.extend({
    createdAt: new Date(),
    createdBy: currentUser
  }, _.pick(options, 'from', 'title', 'text'));

   // Add extra
   _.extend(notification, _.pick(options, 'payload', 'badge', 'sound', 'notId', 'delayUntil'));

  if (Match.test(options.apn, Object)) {
    notification.apn = _.pick(options.apn, 'from', 'title', 'text', 'badge', 'sound', 'notId');
  }

  if (Match.test(options.gcm, Object)) {
    notification.gcm = _.pick(options.gcm, 'image', 'style', 'summaryText', 'picture', 'from', 'title', 'text', 'badge', 'sound', 'notId');
  }

  // Set one token selector, this can be token, array of tokens or query
  if (options.query) {
    // Set query to the json string version fixing #43 and #39
    notification.query = JSON.stringify(options.query);
  } else if (options.token) {
    // Set token
    notification.token = options.token;
  } else if (options.tokens) {
    // Set tokens
    notification.tokens = options.tokens;
  }
  //console.log(options);
  if (typeof options.contentAvailable !== 'undefined') {
    notification.contentAvailable = options.contentAvailable;
  }

  notification.sent = false;
  notification.sending = 0;

  // Validate the notification
  _validateDocument(notification);

  // Try to add the notification to send, we return an id to keep track
  return Push.notifications.insert(notification);
};

Push.allow = function(rules) {
  if (rules.send) {
    Push.notifications.allow({
      'insert': function(userId, notification) {
        // Validate the notification
        _validateDocument(notification);
        // Set the user defined "send" rules
        return rules.send.apply(this, [userId, notification]);
      }
    });
  }
};

Push.deny = function(rules) {
  if (rules.send) {
    Push.notifications.deny({
      'insert': function(userId, notification) {
        // Validate the notification
        _validateDocument(notification);
        // Set the user defined "send" rules
        return rules.send.apply(this, [userId, notification]);
      }
    });
  }
};
<commit_msg>Allow APN category field<commit_after>// This is the match pattern for tokens
_matchToken = Match.OneOf({ apn: String }, { gcm: String });

// Notifications collection
Push.notifications = new Mongo.Collection('_raix_push_notifications');

// This is a general function to validate that the data added to notifications
// is in the correct format. If not this function will throw errors
var _validateDocument = function(notification) {

  // Check the general notification
  check(notification, {
    from: String,
    title: String,
    text: String,
    sent: Match.Optional(Boolean),
    sending: Match.Optional(Match.Integer),
    badge: Match.Optional(Match.Integer),
    sound: Match.Optional(String),
    notId: Match.Optional(Match.Integer),
    contentAvailable: Match.Optional(Match.Integer),
    apn: Match.Optional({
      from: Match.Optional(String),
      title: Match.Optional(String),
      text: Match.Optional(String),
      badge: Match.Optional(Match.Integer),
      sound: Match.Optional(String),
      notId: Match.Optional(Match.Integer),
      category: Match.Optional(String)
    }),
    gcm: Match.Optional({
      from: Match.Optional(String),
      title: Match.Optional(String),
      text: Match.Optional(String),
      image: Match.Optional(String),
      style: Match.Optional(String),
      summaryText: Match.Optional(String),
      picture: Match.Optional(String),
      badge: Match.Optional(Match.Integer),
      sound: Match.Optional(String),
      notId: Match.Optional(Match.Integer)
    }),
    query: Match.Optional(String),
    token: Match.Optional(_matchToken),
    tokens: Match.Optional([_matchToken]),
    payload: Match.Optional(Object),
    delayUntil: Match.Optional(Date),
    createdAt: Date,
    createdBy: Match.OneOf(String, null)
  });

  // Make sure a token selector or query have been set
  if (!notification.token && !notification.tokens && !notification.query) {
    throw new Error('No token selector or query found');
  }

  // If tokens array is set it should not be empty
  if (notification.tokens && !notification.tokens.length) {
    throw new Error('No tokens in array');
  }
};

Push.send = function(options) {
  // If on the client we set the user id - on the server we need an option
  // set or we default to "<SERVER>" as the creator of the notification
  // If current user not set see if we can set it to the logged in user
  // this will only run on the client if Meteor.userId is available
  var currentUser = Meteor.isClient && Meteor.userId && Meteor.userId() ||
          Meteor.isServer && (options.createdBy || '<SERVER>') || null;

  // Rig the notification object
   var notification = _.extend({
    createdAt: new Date(),
    createdBy: currentUser
  }, _.pick(options, 'from', 'title', 'text'));

   // Add extra
   _.extend(notification, _.pick(options, 'payload', 'badge', 'sound', 'notId', 'delayUntil'));

  if (Match.test(options.apn, Object)) {
    notification.apn = _.pick(options.apn, 'from', 'title', 'text', 'badge', 'sound', 'notId', 'category');
  }

  if (Match.test(options.gcm, Object)) {
    notification.gcm = _.pick(options.gcm, 'image', 'style', 'summaryText', 'picture', 'from', 'title', 'text', 'badge', 'sound', 'notId');
  }

  // Set one token selector, this can be token, array of tokens or query
  if (options.query) {
    // Set query to the json string version fixing #43 and #39
    notification.query = JSON.stringify(options.query);
  } else if (options.token) {
    // Set token
    notification.token = options.token;
  } else if (options.tokens) {
    // Set tokens
    notification.tokens = options.tokens;
  }
  //console.log(options);
  if (typeof options.contentAvailable !== 'undefined') {
    notification.contentAvailable = options.contentAvailable;
  }

  notification.sent = false;
  notification.sending = 0;

  // Validate the notification
  _validateDocument(notification);

  // Try to add the notification to send, we return an id to keep track
  return Push.notifications.insert(notification);
};

Push.allow = function(rules) {
  if (rules.send) {
    Push.notifications.allow({
      'insert': function(userId, notification) {
        // Validate the notification
        _validateDocument(notification);
        // Set the user defined "send" rules
        return rules.send.apply(this, [userId, notification]);
      }
    });
  }
};

Push.deny = function(rules) {
  if (rules.send) {
    Push.notifications.deny({
      'insert': function(userId, notification) {
        // Validate the notification
        _validateDocument(notification);
        // Set the user defined "send" rules
        return rules.send.apply(this, [userId, notification]);
      }
    });
  }
};
<|endoftext|>


<commit_before>module.exports = misc;

var keywords = {
    'return':1,
    'switch':1,
    'case':1,
    'default':1,
    'while':1,
    'if':1,
    'do':1,
    'for':1,
    'else':1,
    'try':1,
    'catch':1,
    'undefined':1,
    'true':1,
    'false':1,
    'typeof':1,
    'instanceof':1,
    'null':1,
    'function':1,
    'break':1,
    'continue':1,
    'var':1,
    'in':1,
    'delete':1
};

function misc(babel) {
    var types = babel.types;
    var visitor = {};
    var postVisitor = {};
    postVisitor.MemberExpression = function(path) {
        var node = path.node;
        if (node.property.type != 'Identifier') {
            return;
        }
        if (node.property.name in keywords) {
            path.replaceWith(types.MemberExpression(
                node.object,
                types.StringLiteral(node.property.name),
                true
            ));
        }
    };
    postVisitor.Property = function(path) {
        var node = path.node;
        if (node.key.type != 'Identifier') {
            return;
        }
        if (node.key.name in keywords) {
            var nkey = types.StringLiteral(node.key.name);
            nkey.start = node.key.start;
            nkey.end = node.key.end;
            nkey.loc = node.key.loc;
            node.key = nkey;
        }
    };
    visitor.Program = {exit:function(path) {
        path.traverse(postVisitor);
    }};
    visitor.ClassDeclaration = function(path) {
        if (path.node.id) {
            path.insertAfter({
                type: 'ExpressionStatement',
                expression: {
                    type: 'AssignmentExpression',
                    operator: '=',
                    left: types.MemberExpression(
                        path.node.id,
                        types.Identifier('$$$AWF$$$Class')
                    ),
                    right: types.NumericLiteral(1)
                }
            });
        }
    };
    visitor.MemberExpression = function(path) {
        if (path.parentPath.node.type == 'JSXExpressionContainer'
                && path.parentPath.parentPath.node.type == 'JSXAttribute') {
            var node = path.node;
            var prop = node.property;
            if (!prop.computed && prop.type == 'Identifier')
                prop = types.StringLiteral(prop.name);
            var replace = types.CallExpression(
                types.MemberExpression(
                    types.MemberExpression(
                        types.Identifier('$$$AWF$$$'),
                        types.Identifier('es6')
                    ),
                    types.Identifier('_boundProp')
                ),
                [node.object, prop]
            );
            path.replaceWith(replace);
        }
    };
    function forIter(iterValMem) {
        return function(path) {
            var node = path.node;
            var iterator = path.scope.generateUidIdentifier("iter");
            var body = node.body;
            var replace = {
                type: 'ForStatement',
                init: {
                    type: 'VariableDeclaration',
                    declarations: [{
                        type: 'VariableDeclarator',
                        id: iterator,
                        init: types.CallExpression(
                            types.MemberExpression(
                                types.MemberExpression(
                                    types.Identifier('$$$AWF$$$'),
                                    types.Identifier('es6')
                                ),
                                types.Identifier('_iterator')
                            ),
                            [node.right]
                        )
                    }],
                    kind: 'var'
                },
                test: types.CallExpression(
                    types.MemberExpression(iterator, types.Identifier('next')),
                    []
                ),
                update: null,
                body: body
            };
            var update = node.left;
            var iterVal = types.CallExpression(
                types.MemberExpression(iterator, types.Identifier(iterValMem)),
                []
            );
            if (update.type == 'VariableDeclaration') {
                var decl = update.declarations[0];
                if (decl) {
                    decl.init = iterVal;
                }
            } else {
                update = {
                    type: 'AssignmentExpression',
                    operator: '=',
                    left: update,
                    right: iterVal
                };
            }
            body.body.unshift(update);
            path.replaceWith(replace);
        };
    }
    visitor.ForOfStatement = forIter('value');
    visitor.ForInStatement = forIter('key');

    function updateSignal(target, expression) {
        if (target.type == 'MemberExpression' && !target.object.antiLoop) {
            var prop = target.property;
            if (!target.computed && prop.type == 'Identifier')
                prop = types.StringLiteral(prop.name);
            var objP = this.scope.generateUidIdentifier("o");
            objP.antiLoop = true;
            var keyP = this.scope.generateUidIdentifier("o");
            var retP = this.scope.generateUidIdentifier("o");
            this.replaceWith(types.CallExpression({
                type: 'FunctionExpression',
                params: [objP, keyP, retP],
                body: {
                    type: 'BlockStatement',
                    body: [state({
                        type: 'AssignmentExpression',
                        left: retP,
                        right: expression(objP, keyP),
                        operator: '='
                    }), state(types.CallExpression(
                        types.MemberExpression(
                            types.Identifier('$$$AWF$$$'),
                            types.Identifier('signal')
                        ), [
                        objP, keyP
                    ])),{
                        type: 'ReturnStatement',
                        argument: retP
                    }]
                }
            }, [target.object, prop]));
        }
    }
    function state(expr) {
        return {
            type: 'ExpressionStatement',
            expression: expr
        };
    }
    visitor.AssignmentExpression = function(path) {
        updateSignal.call(path, path.node.left, function(objP, keyP) {
            return {
                type: 'AssignmentExpression',
                left: types.MemberExpression(objP, keyP, true),
                right: path.node.right,
                operator: path.node.operator
            };
        });
    };
    visitor.UpdateExpression = function(path) {
        updateSignal.call(path, path.node.argument, function(objP, keyP) {
            return {
                type: 'UpdateExpression',
                argument: types.MemberExpression(objP, keyP, true),
                operator: path.node.operator,
                prefix: path.node.prefix
            };
        });
    };
    return {visitor:visitor}
}
<commit_msg>BoundObject<commit_after>module.exports = misc;

var keywords = {
    'return':1,
    'switch':1,
    'case':1,
    'default':1,
    'while':1,
    'if':1,
    'do':1,
    'for':1,
    'else':1,
    'try':1,
    'catch':1,
    'undefined':1,
    'true':1,
    'false':1,
    'typeof':1,
    'instanceof':1,
    'null':1,
    'function':1,
    'break':1,
    'continue':1,
    'var':1,
    'in':1,
    'delete':1
};

function misc(babel) {
    var types = babel.types;
    var visitor = {};
    var postVisitor = {};
    postVisitor.MemberExpression = function(path) {
        var node = path.node;
        if (node.property.type != 'Identifier') {
            return;
        }
        if (node.property.name in keywords) {
            path.replaceWith(types.MemberExpression(
                node.object,
                types.StringLiteral(node.property.name),
                true
            ));
        }
    };
    postVisitor.Property = function(path) {
        var node = path.node;
        if (node.key.type != 'Identifier') {
            return;
        }
        if (node.key.name in keywords) {
            var nkey = types.StringLiteral(node.key.name);
            nkey.start = node.key.start;
            nkey.end = node.key.end;
            nkey.loc = node.key.loc;
            node.key = nkey;
        }
    };
    visitor.Program = {exit:function(path) {
        path.traverse(postVisitor);
    }};
    visitor.ClassDeclaration = function(path) {
        if (path.node.id) {
            path.insertAfter({
                type: 'ExpressionStatement',
                expression: {
                    type: 'AssignmentExpression',
                    operator: '=',
                    left: types.MemberExpression(
                        path.node.id,
                        types.Identifier('$$$AWF$$$Class')
                    ),
                    right: types.NumericLiteral(1)
                }
            });
        }
    };
    visitor.MemberExpression = function(path) {
        if (path.parentPath.node.type == 'JSXExpressionContainer'
                && path.parentPath.parentPath.node.type == 'JSXAttribute') {
            var node = path.node;
            var prop = node.property;
            if (!prop.computed && prop.type == 'Identifier')
                prop = types.StringLiteral(prop.name);
            var replace = types.CallExpression(
                types.MemberExpression(
                    types.MemberExpression(
                        types.Identifier('$$$AWF$$$'),
                        types.Identifier('es6')
                    ),
                    types.Identifier('_boundProp')
                ),
                [node.object, prop]
            );
            path.replaceWith(replace);
        }
    };
    function forIter(iterValMem) {
        return function(path) {
            var node = path.node;
            var iterator = path.scope.generateUidIdentifier("iter");
            var body = node.body;
            var replace = {
                type: 'ForStatement',
                init: {
                    type: 'VariableDeclaration',
                    declarations: [{
                        type: 'VariableDeclarator',
                        id: iterator,
                        init: types.CallExpression(
                            types.MemberExpression(
                                types.MemberExpression(
                                    types.Identifier('$$$AWF$$$'),
                                    types.Identifier('es6')
                                ),
                                types.Identifier('_iterator')
                            ),
                            [node.right]
                        )
                    }],
                    kind: 'var'
                },
                test: types.CallExpression(
                    types.MemberExpression(iterator, types.Identifier('next')),
                    []
                ),
                update: null,
                body: body
            };
            var update = node.left;
            var iterVal = types.CallExpression(
                types.MemberExpression(iterator, types.Identifier(iterValMem)),
                []
            );
            if (update.type == 'VariableDeclaration') {
                var decl = update.declarations[0];
                if (decl) {
                    decl.init = iterVal;
                }
            } else {
                update = {
                    type: 'AssignmentExpression',
                    operator: '=',
                    left: update,
                    right: iterVal
                };
            }
            body.body.unshift(update);
            path.replaceWith(replace);
        };
    }
    visitor.ForOfStatement = forIter('value');
    visitor.ForInStatement = forIter('key');

    function updateSignal(target, expression) {
        if (target.type == 'MemberExpression' && !target.object.antiLoop) {
            var prop = target.property;
            if (!target.computed && prop.type == 'Identifier')
                prop = types.StringLiteral(prop.name);
            var objP = this.scope.generateUidIdentifier("o");
            objP.antiLoop = true;
            var keyP = this.scope.generateUidIdentifier("o");
            var retP = this.scope.generateUidIdentifier("o");
            this.replaceWith(types.CallExpression({
                type: 'FunctionExpression',
                params: [objP, keyP, retP],
                body: {
                    type: 'BlockStatement',
                    body: [state({
                        type: 'AssignmentExpression',
                        left: retP,
                        right: expression(objP, keyP),
                        operator: '='
                    }), state(types.CallExpression(
                        types.MemberExpression(
                            types.Identifier('$$$AWF$$$'),
                            types.Identifier('signal')
                        ), [
                        objP, keyP
                    ])),{
                        type: 'ReturnStatement',
                        argument: retP
                    }]
                }
            }, [target.object, prop]));
        }
    }
    function state(expr) {
        return {
            type: 'ExpressionStatement',
            expression: expr
        };
    }
    visitor.AssignmentExpression = function(path) {
        updateSignal.call(path, path.node.left, function(objP, keyP) {
            return {
                type: 'AssignmentExpression',
                left: types.MemberExpression(objP, keyP, true),
                right: path.node.right,
                operator: path.node.operator
            };
        });
    };
    visitor.UpdateExpression = function(path) {
        updateSignal.call(path, path.node.argument, function(objP, keyP) {
            return {
                type: 'UpdateExpression',
                argument: types.MemberExpression(objP, keyP, true),
                operator: path.node.operator,
                prefix: path.node.prefix
            };
        });
    };
    visitor.UnaryExpression = function(path) {
        if (path.node.operator == 'delete') {
            updateSignal.call(path, path.node.argument, function(objP, keyP) {
                return {
                    type: 'UnaryExpression',
                    argument: types.MemberExpression(objP, keyP, true),
                    operator: path.node.operator,
                    prefix: path.node.prefix
                };
            });
        }
    };
    return {visitor:visitor}
}
<|endoftext|>


<commit_before>var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var taskManager = require('../common/manager/taskManager');
var pomelo = require('../pomelo');
var rsa = require("node-bignumber");
var events = require('../util/events');
var utils = require('../util/utils');

module.exports = function(app, opts) {
  return new Component(app, opts);
};

/**
 * Connector component. Receive client requests and attach session with socket.
 *
 * @param {Object} app  current application context
 * @param {Object} opts attach parameters
 *                      opts.connector {Object} provides low level network and protocol details implementation between server and clients.
 */
var Component = function(app, opts) {
  opts = opts || {};
  this.app = app;
  this.connector = getConnector(app, opts);
  this.encode = opts.encode;
  this.decode = opts.decode;
  this.useCrypto = opts.useCrypto;
  this.blacklistFun = opts.blacklistFun;
  this.keys = {};
  this.blacklist = [];

  if(opts.useDict) {
    app.load(pomelo.dictionary, app.get('dictionaryConfig'));
  }

  if(opts.useProtobuf) {
    app.load(pomelo.protobuf, app.get('protobufConfig'));
  }

  if(opts.useCrypto) {
    if (opts.verify != null) {
        this.verify = opts.verify;
    } else {
        this.verify = verifyMessage;
    }
  }

  // component dependencies
  this.server = null;
  this.session = null;
  this.connection = null;
};

var pro = Component.prototype;

pro.name = '__connector__';

pro.start = function(cb) {
  this.server = this.app.components.__server__;
  this.session = this.app.components.__session__;
  this.connection = this.app.components.__connection__;

  // check component dependencies
  if(!this.server) {
    process.nextTick(function() {
      utils.invokeCallback(cb, new Error('fail to start connector component for no server component loaded'));
    });
    return;
  }

  if(!this.session) {
    process.nextTick(function() {
      utils.invokeCallback(cb, new Error('fail to start connector component for no session component loaded'));
    });
    return;
  }

  process.nextTick(cb);
};

pro.afterStart = function(cb) {
  this.connector.start(cb);
  this.connector.on('connection', hostFilter.bind(this, bindEvents));
};

pro.stop = function(force, cb) {
  if(this.connector) {
    this.connector.stop(force, cb);
    this.connector = null;
    return;
  }

  process.nextTick(cb);
};

pro.send = function(reqId, route, msg, recvs, opts, cb) {
  logger.debug('[%s] send message reqId: %s, route: %s, msg: %j, receivers: %j, opts: %j', this.app.serverId, reqId, route, msg, recvs, opts);
  var emsg = msg;
  if(this.encode) {
    // use costumized encode
    emsg = this.encode.call(this, reqId, route, msg);
  } else if(this.connector.encode) {
    // use connector default encode
    emsg = this.connector.encode(reqId, route, msg);
  }

  if(!emsg) {
    process.nextTick(function() {
      utils.invokeCallback(cb, new Error('fail to send message for encode result is empty.'));
      return;
    });
  }

  this.app.components.__pushScheduler__.schedule(reqId, route, emsg,
      recvs, opts, cb);
};

pro.setPubKey = function(id, key) {
  var pubKey = new rsa.Key();
  pubKey.n = new rsa.BigInteger(key.rsa_n, 16);
  pubKey.e = key.rsa_e;
  this.keys[id] = pubKey;
};

pro.getPubKey = function(id) {
  return this.keys[id];
};

var getConnector = function(app, opts) {
  var connector = opts.connector;
  if(!connector) {
    return getDefaultConnector(app, opts);
  }

  if(typeof connector !== 'function') {
    return connector;
  }

  var curServer = app.getCurServer();
  return connector(curServer.clientPort, curServer.host, opts);
};

var getDefaultConnector = function(app, opts) {
  var DefaultConnector = require('../connectors/sioconnector');
  var curServer = app.getCurServer();
  return new DefaultConnector(curServer.clientPort, curServer.host, opts);
};

var hostFilter = function(cb, socket) {
  var ip = socket.remoteAddress.ip;
  var check = function(list) {
    for(var address in list) {
      var exp = new RegExp(list[address]);
      if(exp.test(ip)) {
        socket.disconnect();
        return true;
      }
    }
    return false;
  };
  // dynamical check
  if(this.blacklist.length !== 0 && !!check(this.blacklist)) {
    return;
  }
  // static check
  if(!!this.blacklistFun && typeof this.blacklistFun === 'function') {
    var self = this;
    self.blacklistFun(function(err, list) {
      if(!!err) {
        logger.error('connector blacklist error: %j', err.stack);
        utils.invokeCallback(cb, self, socket);
        return;
      }
      if(!Array.isArray(list)) {
        logger.error('connector blacklist is not array: %j', list);
        utils.invokeCallback(cb, self, socket);
        return;
      }
      if(!!check(list)) {
        return;
      } else {
        utils.invokeCallback(cb, self, socket);
        return;
      }
    });
  } else {
    utils.invokeCallback(cb, this, socket);
  }
};

var bindEvents = function(self, socket) {
  if(self.connection) {
    self.connection.increaseConnectionCount();
    var statisticInfo = self.connection.getStatisticsInfo();
    var curServer = self.app.getCurServer();
    if(statisticInfo.totalConnCount > curServer['max-connections']) {
      logger.warn('the server %s has reached the max connections %s', curServer.id, curServer['max-connections']);
      socket.disconnect();
      return;
    }
  }

  //create session for connection
  var session = getSession(self, socket);
  var closed = false;

  socket.on('disconnect', function() {
    if(closed) {
      return;
    }
    closed = true;
    if(self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });

  socket.on('error', function() {
    if(closed) {
      return;
    }
    closed = true;
    if(self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });

  // new message
  socket.on('message', function(msg) {
    var dmsg = msg;
    if(self.decode) {
      dmsg = self.decode(msg);
    } else if(self.connector.decode) {
      dmsg = self.connector.decode(msg);
    }
    if(!dmsg) {
      // discard invalid message
      return;
    }

    // use rsa crypto
    if(self.useCrypto) {
      var verified = self.verify(self, session, dmsg);
      if(!verified) {
        logger.error('fail to verify the data received from client.');
        return;
      }
    }

    handleMessage(self, session, dmsg);
  }); //on message end
};

/**
 * get session for current connection
 */
var getSession = function(self, socket) {
  var app = self.app, sid = socket.id;
  var session = self.session.get(sid);
  if(session) {
    return session;
  }

  session = self.session.create(sid, app.getServerId(), socket);
  logger.debug('[%s] getSession session is created with session id: %s', app.getServerId(), sid);

  // bind events for session
  socket.on('disconnect', session.closed.bind(session));
  socket.on('error', session.closed.bind(session));
  session.on('closed', onSessionClose.bind(null, app));
  session.on('bind', function(uid) {
    logger.debug('session on [%s] bind with uid: %s', self.app.serverId, uid);
    // update connection statistics if necessary
    if(self.connection) {
      self.connection.addLoginedUser(uid, {
        loginTime: Date.now(),
        uid: uid,
        address: socket.remoteAddress.ip + ':' + socket.remoteAddress.port
      });
    }
    self.app.event.emit(events.BIND_SESSION, session);
  });

  session.on('unbind', function(uid) {
    if(self.connection) {
      self.connection.removeLoginedUser(uid);
    }
    self.app.event.emit(events.UNBIND_SESSION, session);
  });

  return session;
};

var onSessionClose = function(app, session, reason) {
  taskManager.closeQueue(session.id, true);
  app.event.emit(events.CLOSE_SESSION, session);
};

var handleMessage = function(self, session, msg) {
  logger.debug('[%s] handleMessage session id: %s, msg: %j', self.app.serverId, session.id, msg);
  var type = checkServerType(msg.route);
  if(!type) {
    logger.error('invalid route string. route : %j', msg.route);
    return;
  }
  self.server.globalHandle(msg, session.toFrontendSession(), function(err, resp, opts) {
    if(resp && !msg.id) {
      logger.warn('try to response to a notify: %j', msg.route);
      return;
    }
    if (!msg.id && !resp) return;
    if (!resp) resp = {};
    if (!!err){
      resp.code = 500;
    }
    opts = {type: 'response', userOptions: opts || {}};
    // for compatiablity
    opts.isResponse = true;

    self.send(msg.id, msg.route, resp, [session.id], opts,
      function() {});
  });
};

/**
 * Get server type form request message.
 */
var checkServerType = function (route) {
  if(!route) {
    return null;
  }
  var idx = route.indexOf('.');
  if(idx < 0) {
    return null;
  }
  return route.substring(0, idx);
};

var verifyMessage = function (self, session, msg) {
  var sig = msg.body.__crypto__;
  if(!sig) {
    logger.error('receive data from client has no signature [%s]', self.app.serverId);
    return false;
  }

  var pubKey;

  if(!session) {
    logger.error('could not find session.');
    return false;
  }

  if(!session.get('pubKey')) {
    pubKey = self.getPubKey(session.id);
    if(!!pubKey) {
      delete self.keys[session.id];
      session.set('pubKey', pubKey);
    }
    else {
      logger.error('could not get public key, session id is %s', session.id);
      return false;
    }
  }
  else {
    pubKey = session.get('pubKey');
  }

  if(!pubKey.n || !pubKey.e) {
    logger.error('could not verify message without public key [%s]', self.app.serverId);
    return false;
  }

  delete  msg.body.__crypto__;

  var message = JSON.stringify(msg.body);
  if(utils.hasChineseChar(message))
    message = utils.unicodeToUtf8(message);

  return pubKey.verifyString(message, sig);
};
<commit_msg>decodeIO<commit_after>var logger = require('pomelo-logger').getLogger('pomelo', __filename);
var taskManager = require('../common/manager/taskManager');
var pomelo = require('../pomelo');
var rsa = require("node-bignumber");
var events = require('../util/events');
var utils = require('../util/utils');

module.exports = function(app, opts) {
  return new Component(app, opts);
};

/**
 * Connector component. Receive client requests and attach session with socket.
 *
 * @param {Object} app  current application context
 * @param {Object} opts attach parameters
 *                      opts.connector {Object} provides low level network and protocol details implementation between server and clients.
 */
var Component = function(app, opts) {
  opts = opts || {};
  this.app = app;
  this.connector = getConnector(app, opts);
  this.encode = opts.encode;
  this.decode = opts.decode;
  this.useCrypto = opts.useCrypto;
  this.blacklistFun = opts.blacklistFun;
  this.keys = {};
  this.blacklist = [];

  if(opts.useDict) {
    app.load(pomelo.dictionary, app.get('dictionaryConfig'));
  }

  if(opts.useProtobuf) {
    app.load(pomelo.protobuf, app.get('protobufConfig'));
  }

  if(opts.useDecodeIO) {
    app.load(pomelo.decodeIO_protobuf, app.get('protobufConfig'));
  }

  if(opts.useCrypto) {
    if (opts.verify != null) {
        this.verify = opts.verify;
    } else {
        this.verify = verifyMessage;
    }
  }

  // component dependencies
  this.server = null;
  this.session = null;
  this.connection = null;
};

var pro = Component.prototype;

pro.name = '__connector__';

pro.start = function(cb) {
  this.server = this.app.components.__server__;
  this.session = this.app.components.__session__;
  this.connection = this.app.components.__connection__;

  // check component dependencies
  if(!this.server) {
    process.nextTick(function() {
      utils.invokeCallback(cb, new Error('fail to start connector component for no server component loaded'));
    });
    return;
  }

  if(!this.session) {
    process.nextTick(function() {
      utils.invokeCallback(cb, new Error('fail to start connector component for no session component loaded'));
    });
    return;
  }

  process.nextTick(cb);
};

pro.afterStart = function(cb) {
  this.connector.start(cb);
  this.connector.on('connection', hostFilter.bind(this, bindEvents));
};

pro.stop = function(force, cb) {
  if(this.connector) {
    this.connector.stop(force, cb);
    this.connector = null;
    return;
  }

  process.nextTick(cb);
};

pro.send = function(reqId, route, msg, recvs, opts, cb) {
  logger.debug('[%s] send message reqId: %s, route: %s, msg: %j, receivers: %j, opts: %j', this.app.serverId, reqId, route, msg, recvs, opts);
  var emsg = msg;
  if(this.encode) {
    // use costumized encode
    emsg = this.encode.call(this, reqId, route, msg);
  } else if(this.connector.encode) {
    // use connector default encode
    emsg = this.connector.encode(reqId, route, msg);
  }

  if(!emsg) {
    process.nextTick(function() {
      utils.invokeCallback(cb, new Error('fail to send message for encode result is empty.'));
      return;
    });
  }

  this.app.components.__pushScheduler__.schedule(reqId, route, emsg,
      recvs, opts, cb);
};

pro.setPubKey = function(id, key) {
  var pubKey = new rsa.Key();
  pubKey.n = new rsa.BigInteger(key.rsa_n, 16);
  pubKey.e = key.rsa_e;
  this.keys[id] = pubKey;
};

pro.getPubKey = function(id) {
  return this.keys[id];
};

var getConnector = function(app, opts) {
  var connector = opts.connector;
  if(!connector) {
    return getDefaultConnector(app, opts);
  }

  if(typeof connector !== 'function') {
    return connector;
  }

  var curServer = app.getCurServer();
  return connector(curServer.clientPort, curServer.host, opts);
};

var getDefaultConnector = function(app, opts) {
  var DefaultConnector = require('../connectors/sioconnector');
  var curServer = app.getCurServer();
  return new DefaultConnector(curServer.clientPort, curServer.host, opts);
};

var hostFilter = function(cb, socket) {
  var ip = socket.remoteAddress.ip;
  var check = function(list) {
    for(var address in list) {
      var exp = new RegExp(list[address]);
      if(exp.test(ip)) {
        socket.disconnect();
        return true;
      }
    }
    return false;
  };
  // dynamical check
  if(this.blacklist.length !== 0 && !!check(this.blacklist)) {
    return;
  }
  // static check
  if(!!this.blacklistFun && typeof this.blacklistFun === 'function') {
    var self = this;
    self.blacklistFun(function(err, list) {
      if(!!err) {
        logger.error('connector blacklist error: %j', err.stack);
        utils.invokeCallback(cb, self, socket);
        return;
      }
      if(!Array.isArray(list)) {
        logger.error('connector blacklist is not array: %j', list);
        utils.invokeCallback(cb, self, socket);
        return;
      }
      if(!!check(list)) {
        return;
      } else {
        utils.invokeCallback(cb, self, socket);
        return;
      }
    });
  } else {
    utils.invokeCallback(cb, this, socket);
  }
};

var bindEvents = function(self, socket) {
  if(self.connection) {
    self.connection.increaseConnectionCount();
    var statisticInfo = self.connection.getStatisticsInfo();
    var curServer = self.app.getCurServer();
    if(statisticInfo.totalConnCount > curServer['max-connections']) {
      logger.warn('the server %s has reached the max connections %s', curServer.id, curServer['max-connections']);
      socket.disconnect();
      return;
    }
  }

  //create session for connection
  var session = getSession(self, socket);
  var closed = false;

  socket.on('disconnect', function() {
    if(closed) {
      return;
    }
    closed = true;
    if(self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });

  socket.on('error', function() {
    if(closed) {
      return;
    }
    closed = true;
    if(self.connection) {
      self.connection.decreaseConnectionCount(session.uid);
    }
  });

  // new message
  socket.on('message', function(msg) {
    var dmsg = msg;
    if(self.decode) {
      dmsg = self.decode(msg);
    } else if(self.connector.decode) {
      dmsg = self.connector.decode(msg);
    }
    if(!dmsg) {
      // discard invalid message
      return;
    }

    // use rsa crypto
    if(self.useCrypto) {
      var verified = self.verify(self, session, dmsg);
      if(!verified) {
        logger.error('fail to verify the data received from client.');
        return;
      }
    }

    handleMessage(self, session, dmsg);
  }); //on message end
};

/**
 * get session for current connection
 */
var getSession = function(self, socket) {
  var app = self.app, sid = socket.id;
  var session = self.session.get(sid);
  if(session) {
    return session;
  }

  session = self.session.create(sid, app.getServerId(), socket);
  logger.debug('[%s] getSession session is created with session id: %s', app.getServerId(), sid);

  // bind events for session
  socket.on('disconnect', session.closed.bind(session));
  socket.on('error', session.closed.bind(session));
  session.on('closed', onSessionClose.bind(null, app));
  session.on('bind', function(uid) {
    logger.debug('session on [%s] bind with uid: %s', self.app.serverId, uid);
    // update connection statistics if necessary
    if(self.connection) {
      self.connection.addLoginedUser(uid, {
        loginTime: Date.now(),
        uid: uid,
        address: socket.remoteAddress.ip + ':' + socket.remoteAddress.port
      });
    }
    self.app.event.emit(events.BIND_SESSION, session);
  });

  session.on('unbind', function(uid) {
    if(self.connection) {
      self.connection.removeLoginedUser(uid);
    }
    self.app.event.emit(events.UNBIND_SESSION, session);
  });

  return session;
};

var onSessionClose = function(app, session, reason) {
  taskManager.closeQueue(session.id, true);
  app.event.emit(events.CLOSE_SESSION, session);
};

var handleMessage = function(self, session, msg) {
  logger.debug('[%s] handleMessage session id: %s, msg: %j', self.app.serverId, session.id, msg);
  var type = checkServerType(msg.route);
  if(!type) {
    logger.error('invalid route string. route : %j', msg.route);
    return;
  }
  self.server.globalHandle(msg, session.toFrontendSession(), function(err, resp, opts) {
    if(resp && !msg.id) {
      logger.warn('try to response to a notify: %j', msg.route);
      return;
    }
    if (!msg.id && !resp) return;
    if (!resp) resp = {};
    if (!!err){
      resp.code = 500;
    }
    opts = {type: 'response', userOptions: opts || {}};
    // for compatiablity
    opts.isResponse = true;

    self.send(msg.id, msg.route, resp, [session.id], opts,
      function() {});
  });
};

/**
 * Get server type form request message.
 */
var checkServerType = function (route) {
  if(!route) {
    return null;
  }
  var idx = route.indexOf('.');
  if(idx < 0) {
    return null;
  }
  return route.substring(0, idx);
};

var verifyMessage = function (self, session, msg) {
  var sig = msg.body.__crypto__;
  if(!sig) {
    logger.error('receive data from client has no signature [%s]', self.app.serverId);
    return false;
  }

  var pubKey;

  if(!session) {
    logger.error('could not find session.');
    return false;
  }

  if(!session.get('pubKey')) {
    pubKey = self.getPubKey(session.id);
    if(!!pubKey) {
      delete self.keys[session.id];
      session.set('pubKey', pubKey);
    }
    else {
      logger.error('could not get public key, session id is %s', session.id);
      return false;
    }
  }
  else {
    pubKey = session.get('pubKey');
  }

  if(!pubKey.n || !pubKey.e) {
    logger.error('could not verify message without public key [%s]', self.app.serverId);
    return false;
  }

  delete  msg.body.__crypto__;

  var message = JSON.stringify(msg.body);
  if(utils.hasChineseChar(message))
    message = utils.unicodeToUtf8(message);

  return pubKey.verifyString(message, sig);
};
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

var moduleDecorator = require('./module-decorator');

module.exports = configurationLoader;

function configurationLoader(customConfig) {
  var config = customConfig || {};
  _.defaults(config, getDefaultConfig());

  moduleDecorator.setConfig(config);
  global[config.globalRegistrationModuleName] = moduleDecorator.decorator;

  return config;
}

function getDefaultConfig() {
  return {
    predicateFilePicker: function() {
      return [
        'plugins/**/index.js',
        'plugins/**/*.module.js'
      ];
    },
    globalRegistrationModuleName: 'KlarkModule',
    base: process.cwd(),
    logLevel: 'off',
    moduleAlias: {
      '_': '$lodash'
    }
  };
}<commit_msg>add q module as alias<commit_after>'use strict';

var _ = require('lodash');

var moduleDecorator = require('./module-decorator');

module.exports = configurationLoader;

function configurationLoader(customConfig) {
  var config = customConfig || {};
  _.defaults(config, getDefaultConfig());

  moduleDecorator.setConfig(config);
  global[config.globalRegistrationModuleName] = moduleDecorator.decorator;

  return config;
}

function getDefaultConfig() {
  return {
    predicateFilePicker: function() {
      return [
        'plugins/**/index.js',
        'plugins/**/*.module.js'
      ];
    },
    globalRegistrationModuleName: 'KlarkModule',
    base: process.cwd(),
    logLevel: 'off',
    moduleAlias: {
      '_': '$lodash',
      'q': '$q'
    }
  };
}<|endoftext|>


<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2015, Joyent, Inc.
 */

/**
 * A Transform stream to apply changes to napi/portolan based on
 * incoming UFDS objects.
 */

var assert = require('assert-plus');
var NAPI = require('sdc-clients').NAPI;
var Transform = require('stream').Transform;
var util = require('util');
var uuid = require('node-uuid');
var vasync = require('vasync');

function DefaultFabricSetupStream(opts) {
    assert.object(opts, 'opts');
    assert.object(opts.log, 'opts.log');
    assert.object(opts.napi, 'opts.napi');
    assert.object(opts.default_vlan, 'opts.default_vlan');
    assert.string(opts.default_vlan.name, 'default vlan name');
    assert.number(opts.default_vlan.vlan_id, 'default vlan id');
    assert.object(opts.default_network, 'network');
    assert.string(opts.default_network.gateway, 'default gateway');
    assert.string(opts.default_network.name, 'default name');
    assert.string(opts.default_network.provision_start_ip, 'start_ip');
    assert.string(opts.default_network.provision_end_ip, 'end_ip');
    assert.string(opts.default_network.resolvers, 'resolvers');
    assert.number(opts.default_network.vlan_id, 'vlan_id');

    Transform.call(this, { objectMode: true });

    var self = this;

    self.log = opts.log.child({ component: 'DefaultFabricSetupStream' }, true);
    self.dead = false;

    self.napiClient = new NAPI(opts.napi);

    function createDefaultVlan(_opts, cb) {
        assert.object(_opts, '_opts');
        assert.string(_opts.uuid, 'uuid');
        assert.string(_opts.requestId, 'requestId');

        var reqOptions = { headers: { 'x-request-id': _opts.requestId }};

        self.napiClient.listFabricVLANs(_opts.uuid,
            reqOptions,
            function (err, vlans) {
            var defaultVlan;
            if (err) {
                return cb(err);
            }
            // XXX - this vlan is not necessarily the right one.
            defaultVlan = vlans.reduce(function (acc, v) {
                return v.name === opts.default_vlan.name ? v : null;
            }, null);
            if (defaultVlan) {
                self.log.info({ vlan: defaultVlan, user: _opts.uuid },
                    'Default vlan exists for user');
                _opts.defaultVlan = defaultVlan;
                return cb();
            }

            function create() {
                self.napiClient.createFabricVLAN(_opts.uuid,
                    opts.default_vlan,
                reqOptions,
                    function (_err, vlan) {
                    var timeout = 10000;
                    if (_err &&
                        _err.statusCode >= 500 && _err.statusCode < 600) {
                        self.log.error({ err: _err, user: _opts.uuid,
                            requestId: _opts.requestId },
                            'Server error creating vlan for user %s. ' +
                            'Retrying in %sms',
                                _opts.uuid, timeout);
                        setTimeout(create, timeout);
                    } else if (_err) {
                        self.log.error({ err: _err },
                        'Error creating fabric vlan for user');
                        return cb(_err);
                    }
                _opts.defaultVlan = vlan;
                    self.log.info({ vlan: vlan, user: _opts.uuid },
                    'Created default fabric vlan for user');
                    return cb();
                });
            }
            create();
        });
    }

    function createDefaultNetwork(_opts, cb) {
        assert.object(_opts, 'opts');
        assert.string(_opts.uuid, 'uuid');
        assert.string(_opts.requestId, 'requestId');

        var reqOptions = { headers: { 'x-request-id': _opts.requestId }};

        self.napiClient.listFabricNetworks(_opts.uuid,
            opts.default_network.vlan_id,
            reqOptions,
            function (err, networks) {
            var defaultNetwork;
            if (err) {
                self.log.error({ err: err, user: _opts.uuid,
                    requestId: _opts.requestId },
                    'Error creating default fabric network for user');
                return cb(err);
            }
            // XXX - this network is not necessarily the right one.
            defaultNetwork = networks.reduce(function (acc, n) {
                return n.name === opts.default_network.name ? n : null;
            }, null);
            if (defaultNetwork) {
                self.log.info({ network: defaultNetwork, user: _opts.uuid,
                    requestId: _opts.requestId },
                    'Default network exists for user',
                    defaultNetwork.uuid, _opts.uuid);
                _opts.defaultNetwork = defaultNetwork;
                return cb();
            }

            function create() {
                self.napiClient.createFabricNetwork(_opts.uuid,
                    opts.default_network.vlan_id, opts.default_network,
                reqOptions,
                    function (_err, network) {
                        var timeout = 10000;
                        if (_err &&
                            _err.statusCode >= 500 && _err.statusCode < 600) {
                            self.log.warn({ err: _err },
                                'Server error creating network for user %s. ' +
                                'Retrying in %sms',
                                    _opts.uuid, timeout);
                            setTimeout(create, timeout);
                        } else if (_err) {
                            self.log.error({ err: _err, user: _opts.uuid },
                                'Error creating network for user %s',
                                _opts.uuid);
                            return cb(_err);
                        }
                        self.log.info({ network: network, user: _opts.uuid },
                            'create default network %s for user %s',
                            network.uuid, _opts.uuid);
                    _opts.defaultNetwork = network;
                        return cb();
                });
            }
            create();
        });
    }

    // expects objs like:
    // {
    //     user: { UFDS user },
    //     changenumber: 12,
    //     requestId: UUID
    // }
    function napiWorker(obj, cb) {
        assert.string(obj.user.uuid, 'user uuid');
        assert.string(obj.requestId, 'requestId');

        if (self.dead) {
            return cb();
        }

        self.log.debug({ user: obj.user }, 'Adding network for user');

        var arg = {
            uuid: obj.user.uuid,
            requestId: obj.requestId
        };

        vasync.pipeline({
            arg: arg,
            funcs: [
                createDefaultVlan,
                createDefaultNetwork
            ]
        }, function (err, results) {
            if (err) {
                return cb(err);
            }
            obj.vlan = arg.defaultVlan;
            obj.network = arg.defaultNetwork;
            self.log.info({ obj: obj },
                'created default overlay for user');
            self.push(obj);
            return cb();
        });
    }

    this.napiQueue = vasync.queue(wrapWorker(napiWorker), 10);
}
util.inherits(DefaultFabricSetupStream, Transform);
module.exports = DefaultFabricSetupStream;

// wraps a stream object such that it calls the stream cb when the task
// is started by vasync.queue
function wrapTask(obj, streamCb) {
    return function task() {
        streamCb();
        return obj;
    };
}

// wraps a typical vasync.queue worker to call a stream callback
function wrapWorker(workerFunc) {
    return function worker(wrappedObj, cb) {
        workerFunc(wrappedObj(), cb);
    };
}

DefaultFabricSetupStream.prototype._transform =
function _transform(obj, _, cb) {
    var self = this;

    if (self.dead) {
        return cb();
    }

    self.napiQueue.push(wrapTask(obj, cb), function (err) {
        // TODO: emit error here?

        if (err) {
            // does error/failure here depend on the type of error?
            // this is the appropriate level of encapsulation.
            self.emit('failure', err, obj);

            // most errors imply failure as well
            // some errors are 'success' at least that we don't want
            // to repeat - typically pass those through.
        }

        self.log.debug({ npending: self.napiQueue.npending,
        queued: self.napiQueue.queued.length },
        'finish: napiQueue has %d tasks running, %d queued',
        self.napiQueue.npending, self.napiQueue.queued.length);
    });
    self.log.debug({ npending: self.napiQueue.npending,
        queued: self.napiQueue.queued.length },
        'pushed: napiQueue has %d tasks running, %d queued',
        self.napiQueue.npending, self.napiQueue.queued.length);
};

DefaultFabricSetupStream.prototype.close = function close() {
    var self = this;
    self.log.debug('close: entered');
    if (self.dead) {
        setImmediate(self.emit.bind(self, 'close'));
        return;
    }

    self.dead = true;
    self.push(null);
    self.emit('close');

    self.log.debug('close: done');
};
<commit_msg>NAPI-250 watcher crashing when vlan exists.<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2015, Joyent, Inc.
 */

/**
 * A Transform stream to apply changes to napi/portolan based on
 * incoming UFDS objects.
 */

var assert = require('assert-plus');
var NAPI = require('sdc-clients').NAPI;
var Transform = require('stream').Transform;
var util = require('util');
var uuid = require('node-uuid');
var vasync = require('vasync');

function DefaultFabricSetupStream(opts) {
    assert.object(opts, 'opts');
    assert.object(opts.log, 'opts.log');
    assert.object(opts.napi, 'opts.napi');
    assert.object(opts.default_vlan, 'opts.default_vlan');
    assert.string(opts.default_vlan.name, 'default vlan name');
    assert.number(opts.default_vlan.vlan_id, 'default vlan id');
    assert.object(opts.default_network, 'network');
    assert.string(opts.default_network.gateway, 'default gateway');
    assert.string(opts.default_network.name, 'default name');
    assert.string(opts.default_network.provision_start_ip, 'start_ip');
    assert.string(opts.default_network.provision_end_ip, 'end_ip');
    assert.string(opts.default_network.resolvers, 'resolvers');
    assert.number(opts.default_network.vlan_id, 'vlan_id');

    Transform.call(this, { objectMode: true });

    var self = this;

    self.log = opts.log.child({ component: 'DefaultFabricSetupStream' }, true);
    self.dead = false;

    self.napiClient = new NAPI(opts.napi);

    function createDefaultVlan(_opts, cb) {
        assert.object(_opts, '_opts');
        assert.string(_opts.uuid, 'uuid');
        assert.string(_opts.requestId, 'requestId');

        var reqOptions = { headers: { 'x-request-id': _opts.requestId }};

        self.napiClient.listFabricVLANs(_opts.uuid,
            reqOptions,
            function (err, vlans) {
            var defaultVlan;
            if (err) {
                return cb(err);
            }
            // XXX - this vlan is not necessarily the right one.
            defaultVlan = vlans.reduce(function (acc, v) {
                return acc || (v.name === opts.default_vlan.name ? v : null);
            }, null);
            if (defaultVlan) {
                self.log.info({ vlan: defaultVlan, user: _opts.uuid },
                    'Default vlan exists for user');
                _opts.defaultVlan = defaultVlan;
                return cb();
            }

            function create() {
                self.napiClient.createFabricVLAN(_opts.uuid,
                    opts.default_vlan,
                reqOptions,
                    function (_err, vlan) {
                    var timeout = 10000;
                    if (_err &&
                        _err.statusCode >= 500 && _err.statusCode < 600) {
                        self.log.error({ err: _err, user: _opts.uuid,
                            requestId: _opts.requestId },
                            'Server error creating vlan for user %s. ' +
                            'Retrying in %sms',
                                _opts.uuid, timeout);
                        setTimeout(create, timeout);
                    } else if (_err) {
                        self.log.error({ err: _err },
                        'Error creating fabric vlan for user');
                        return cb(_err);
                    }
                _opts.defaultVlan = vlan;
                    self.log.info({ vlan: vlan, user: _opts.uuid },
                    'Created default fabric vlan for user');
                    return cb();
                });
            }
            create();
        });
    }

    function createDefaultNetwork(_opts, cb) {
        assert.object(_opts, 'opts');
        assert.string(_opts.uuid, 'uuid');
        assert.string(_opts.requestId, 'requestId');

        var reqOptions = { headers: { 'x-request-id': _opts.requestId }};

        self.napiClient.listFabricNetworks(_opts.uuid,
            opts.default_network.vlan_id,
            reqOptions,
            function (err, networks) {
            var defaultNetwork;
            if (err) {
                self.log.error({ err: err, user: _opts.uuid,
                    requestId: _opts.requestId },
                    'Error creating default fabric network for user');
                return cb(err);
            }
            // XXX - this network is not necessarily the right one.
            defaultNetwork = networks.reduce(function (acc, n) {
                return n.name === opts.default_network.name ? n : null;
            }, null);
            if (defaultNetwork) {
                self.log.info({ network: defaultNetwork, user: _opts.uuid,
                    requestId: _opts.requestId },
                    'Default network exists for user',
                    defaultNetwork.uuid, _opts.uuid);
                _opts.defaultNetwork = defaultNetwork;
                return cb();
            }

            function create() {
                self.napiClient.createFabricNetwork(_opts.uuid,
                    opts.default_network.vlan_id, opts.default_network,
                reqOptions,
                    function (_err, network) {
                        var timeout = 10000;
                        if (_err &&
                            _err.statusCode >= 500 && _err.statusCode < 600) {
                            self.log.warn({ err: _err },
                                'Server error creating network for user %s. ' +
                                'Retrying in %sms',
                                    _opts.uuid, timeout);
                            setTimeout(create, timeout);
                        } else if (_err) {
                            self.log.error({ err: _err, user: _opts.uuid },
                                'Error creating network for user %s',
                                _opts.uuid);
                            return cb(_err);
                        }
                        self.log.info({ network: network, user: _opts.uuid },
                            'create default network %s for user %s',
                            network.uuid, _opts.uuid);
                    _opts.defaultNetwork = network;
                        return cb();
                });
            }
            create();
        });
    }

    // expects objs like:
    // {
    //     user: { UFDS user },
    //     changenumber: 12,
    //     requestId: UUID
    // }
    function napiWorker(obj, cb) {
        assert.string(obj.user.uuid, 'user uuid');
        assert.string(obj.requestId, 'requestId');

        if (self.dead) {
            return cb();
        }

        self.log.debug({ user: obj.user }, 'Adding network for user');

        var arg = {
            uuid: obj.user.uuid,
            requestId: obj.requestId
        };

        vasync.pipeline({
            arg: arg,
            funcs: [
                createDefaultVlan,
                createDefaultNetwork
            ]
        }, function (err, results) {
            if (err) {
                return cb(err);
            }
            obj.vlan = arg.defaultVlan;
            obj.network = arg.defaultNetwork;
            self.log.info({ obj: obj },
                'created default overlay for user');
            self.push(obj);
            return cb();
        });
    }

    this.napiQueue = vasync.queue(wrapWorker(napiWorker), 10);
}
util.inherits(DefaultFabricSetupStream, Transform);
module.exports = DefaultFabricSetupStream;

// wraps a stream object such that it calls the stream cb when the task
// is started by vasync.queue
function wrapTask(obj, streamCb) {
    return function task() {
        streamCb();
        return obj;
    };
}

// wraps a typical vasync.queue worker to call a stream callback
function wrapWorker(workerFunc) {
    return function worker(wrappedObj, cb) {
        workerFunc(wrappedObj(), cb);
    };
}

DefaultFabricSetupStream.prototype._transform =
function _transform(obj, _, cb) {
    var self = this;

    if (self.dead) {
        return cb();
    }

    self.napiQueue.push(wrapTask(obj, cb), function (err) {
        // TODO: emit error here?

        if (err) {
            // does error/failure here depend on the type of error?
            // this is the appropriate level of encapsulation.
            self.emit('failure', err, obj);

            // most errors imply failure as well
            // some errors are 'success' at least that we don't want
            // to repeat - typically pass those through.
        }

        self.log.debug({ npending: self.napiQueue.npending,
        queued: self.napiQueue.queued.length },
        'finish: napiQueue has %d tasks running, %d queued',
        self.napiQueue.npending, self.napiQueue.queued.length);
    });
    self.log.debug({ npending: self.napiQueue.npending,
        queued: self.napiQueue.queued.length },
        'pushed: napiQueue has %d tasks running, %d queued',
        self.napiQueue.npending, self.napiQueue.queued.length);
};

DefaultFabricSetupStream.prototype.close = function close() {
    var self = this;
    self.log.debug('close: entered');
    if (self.dead) {
        setImmediate(self.emit.bind(self, 'close'));
        return;
    }

    self.dead = true;
    self.push(null);
    self.emit('close');

    self.log.debug('close: done');
};
<|endoftext|>


<commit_before>var Utils         = require("../../utils")
  , AbstractQuery = require('../abstract/query')

module.exports = (function() {
  var Query = function(client, sequelize, callee, options) {
    this.client    = client
    this.callee    = callee
    this.sequelize = sequelize
    this.options   = Utils._.extend({
      logging: console.log,
      plain: false,
      raw: false
    }, options || {})

    this.checkLoggingOption()
  }

  Utils.inherit(Query, AbstractQuery)

  Query.prototype.run = function(sql) {
    this.sql = sql

    bindClient.call(this)

    if(this.options.logging !== false) {
      this.options.logging('Executing: ' + this.sql)
    }

    this.client.query(this.sql, function(err, results, fields) {
      unbindClient.call(this)

      this.emit('sql', this.sql)

      if (err) {
        this.emit('error', err, this.callee)
      } else {
        if (Array.isArray(results) && !!results[0] && Utils._.keys(results[0]).length === 1) {
          results = results.map(function(result){ return Utils._.values(result)[0] })
        }

        this.emit('success', this.formatResults(results))
      }
    }.bind(this)).setMaxListeners(100)

    return this
  }

  //private

  var onClientError = function(err) {
    unbindClient.call(this)
    this.emit('error', err, this.callee)
  }

  var bindClient = function() {
    this.client.on('error', onClientError.bind(this))
  }

  var unbindClient = function() {
    this.client.removeListener('error', onClientError.bind(this))
  }

  return Query
})()
<commit_msg>removed error observing of sql client<commit_after>var Utils         = require("../../utils")
  , AbstractQuery = require('../abstract/query')

module.exports = (function() {
  var Query = function(client, sequelize, callee, options) {
    this.client    = client
    this.callee    = callee
    this.sequelize = sequelize
    this.options   = Utils._.extend({
      logging: console.log,
      plain: false,
      raw: false
    }, options || {})

    this.checkLoggingOption()
  }

  Utils.inherit(Query, AbstractQuery)

  Query.prototype.run = function(sql) {
    this.sql = sql

    if(this.options.logging !== false) {
      this.options.logging('Executing: ' + this.sql)
    }

    this.client.query(this.sql, function(err, results, fields) {
      this.emit('sql', this.sql)

      if (err) {
        this.emit('error', err, this.callee)
      } else {
        if (Array.isArray(results) && !!results[0] && Utils._.keys(results[0]).length === 1) {
          results = results.map(function(result){ return Utils._.values(result)[0] })
        }

        this.emit('success', this.formatResults(results))
      }
    }.bind(this)).setMaxListeners(100)

    return this
  }

  return Query
})()
<|endoftext|>


<commit_before>import { createBrowserHistory as createHistory } from 'history';
import URLSearchParams from '@ungap/url-search-params';

/**
 * Sets up bidirectional synchronisation between a Redux store and window
 * location query parameters.
 *
 * @param {Object} options.store - The redux store object (= an object `{dispatch, getState}`).
 * @param {Object} options.params - The query parameters in the location to keep in sync.
 * @param {function value => action} options.params[].action - The action creator to be invoked with
 *     the parameter value. Should return an action that sets this value in the store.
 * @param {function state => value} options.params[].selector - The function that gets the value
 *     given the state.
 * @param {*} [options.params[].defaultValue] - The value corresponding to absence of the parameter.
 *     You may want this to equal the state's default/initial value. Default: `undefined`.
 * @param {function} [options.params[].valueToString] - The inverse of stringToValue. Specifies how
 *     to cast the value to a string, to be used in the URL. Defaults to javascript's automatic
 *     string conversion.
 * @param {function} [options.params[].stringToValue] - The inverse of valueToString. Specifies how
 *     to parse the parameter's string value to your desired value type. Defaults to the identity
 *     function (i.e. you get the string as it is).
 * @param {string} options.initialTruth - If set, indicates whose values to sync to the other,
 *     initially. Can be either `'location'` or `'store'`. If not set, the first of them that
 *     changes will set the other, which is not recommended. Usually you will want to use
 *     `location`.
 * @param {boolean} [options.replaceState] - If truthy, update location using
 *     history.replaceState instead of history.pushState, to not fill the browser history.
 * @param {Object} [options.history] - If you use the 'history' module, e.g. when using a router,
 *     pass your history object here in order to ensure all code uses the same instance.
 */
function ReduxQuerySync({
    store,
    params,
    replaceState,
    initialTruth,
    history = createHistory(),
}) {
    const { dispatch } = store

    // Two bits of state used to not respond to self-induced updates.
    let ignoreLocationUpdate = false
    let ignoreStateUpdate = false

    // Keeps the last seen values for comparing what has changed.
    let lastQueryValues

    // Helper function to support history module v3 as well as v4.
    function getLocation(history) {
        if (Object.hasOwnProperty.call(history, 'location')) {
            return history.location
        } else if (Object.hasOwnProperty.call(history, 'getCurrentLocation')) {
            return history.getCurrentLocation()
        }
    }

    function getQueryValues(location) {
        const locationParams = new URLSearchParams(location.search);
        const queryValues = {}
        Object.keys(params).forEach(param => {
            const { defaultValue, stringToValue = s => s } = params[param]
            const valueString = locationParams.get(param)
            const value = (valueString === null)
                ? defaultValue
                : stringToValue(valueString)
            queryValues[param] = value
        })
        return queryValues
    }

    function handleLocationUpdate(location) {
        // Support history v5
        if (location.location != null) {
            location = location.location;
        }

        // Ignore the event if the location update was induced by ourselves.
        if (ignoreLocationUpdate) return

        const state = store.getState()

        // Read the values of the watched parameters
        const queryValues = getQueryValues(location)

        // For each parameter value that changed, we dispatch the corresponding action.
        const actionsToDispatch = []
        Object.keys(queryValues).forEach(param => {
            const value = queryValues[param]
            // Process the parameter both on initialisation and if it has changed since last time.
            // (should we just do this unconditionally?)
            if (lastQueryValues === undefined || lastQueryValues[param] !== value) {
                const { selector, action } = params[param]

                // Dispatch the action to update the state if needed.
                // (except on initialisation, this should always be needed)
                if (selector(state) !== value) {
                    actionsToDispatch.push(action(value))
                }
            }
        })

        lastQueryValues = queryValues

        ignoreStateUpdate = true
        actionsToDispatch.forEach(action => {
            dispatch(action)
        })
        ignoreStateUpdate = false

        // Update the location the again: reducers may have e.g. corrected invalid values.
        handleStateUpdate({replaceState: true})
    }

    function handleStateUpdate({replaceState}) {
        if (ignoreStateUpdate) return

        const state = store.getState()
        const location = getLocation(history)

        // Parse the current location's query string.
        const locationParams = new URLSearchParams(location.search);

        // Replace each configured parameter with its value in the state.
        Object.keys(params).forEach(param => {
            const { selector, defaultValue, valueToString = v => `${v}` } = params[param]
            const value = selector(state)
            if (value === defaultValue) {
                locationParams.delete(param)
            } else {
                locationParams.set(param, valueToString(value))
            }
            lastQueryValues[param] = value
        })
        const newLocationSearchString = `?${locationParams}`
        const oldLocationSearchString = location.search || '?'

        // Only update location if anything changed.
        if (newLocationSearchString !== oldLocationSearchString) {
            // Update location (but prevent triggering a state update).
            ignoreLocationUpdate = true
            const newLocation = {
                pathname: location.pathname,
                search: newLocationSearchString,
                hash: location.hash,
                state: location.state,
            }
            replaceState
                ? history.replace(newLocation)
                : history.push(newLocation)
            ignoreLocationUpdate = false
        }
    }

    // Sync location to store on every location change, and vice versa.
    const unsubscribeFromLocation = history.listen(handleLocationUpdate)
    const unsubscribeFromStore = store.subscribe(() => handleStateUpdate({replaceState}))

    // Sync location to store now, or vice versa, or neither.
    if (initialTruth === 'location') {
        handleLocationUpdate(getLocation(history))
    } else {
        // Just set the last seen values to later compare what changed.
        lastQueryValues = getQueryValues(getLocation(history))
    }
    if (initialTruth === 'store') {
        handleStateUpdate({replaceState: true})
    }

    return function unsubscribe() {
        unsubscribeFromLocation()
        unsubscribeFromStore()
    }
}

/**
 * For convenience, one can set up the synchronisation by passing this enhancer to createStore.
 *
 * @example
 *
 *     const storeEnhancer = ReduxQuerySync.enhancer({params, initialTruth: 'location'})
 *     const store = createStore(reducer, initialState, storeEnhancer)
 *
 * Arguments are equal to those of ReduxQuerySync itself, except that `store` can now be omitted.
 */
ReduxQuerySync.enhancer = function makeStoreEnhancer(config) {
    return storeCreator => (reducer, initialState, enhancer) => {
        // Create the store as usual.
        const store = storeCreator(reducer, initialState, enhancer)

        // Hook up our listeners.
        ReduxQuerySync({store, ...config})

        return store
    }
}

export default ReduxQuerySync
<commit_msg>use strict equality test<commit_after>import { createBrowserHistory as createHistory } from 'history';
import URLSearchParams from '@ungap/url-search-params';

/**
 * Sets up bidirectional synchronisation between a Redux store and window
 * location query parameters.
 *
 * @param {Object} options.store - The redux store object (= an object `{dispatch, getState}`).
 * @param {Object} options.params - The query parameters in the location to keep in sync.
 * @param {function value => action} options.params[].action - The action creator to be invoked with
 *     the parameter value. Should return an action that sets this value in the store.
 * @param {function state => value} options.params[].selector - The function that gets the value
 *     given the state.
 * @param {*} [options.params[].defaultValue] - The value corresponding to absence of the parameter.
 *     You may want this to equal the state's default/initial value. Default: `undefined`.
 * @param {function} [options.params[].valueToString] - The inverse of stringToValue. Specifies how
 *     to cast the value to a string, to be used in the URL. Defaults to javascript's automatic
 *     string conversion.
 * @param {function} [options.params[].stringToValue] - The inverse of valueToString. Specifies how
 *     to parse the parameter's string value to your desired value type. Defaults to the identity
 *     function (i.e. you get the string as it is).
 * @param {string} options.initialTruth - If set, indicates whose values to sync to the other,
 *     initially. Can be either `'location'` or `'store'`. If not set, the first of them that
 *     changes will set the other, which is not recommended. Usually you will want to use
 *     `location`.
 * @param {boolean} [options.replaceState] - If truthy, update location using
 *     history.replaceState instead of history.pushState, to not fill the browser history.
 * @param {Object} [options.history] - If you use the 'history' module, e.g. when using a router,
 *     pass your history object here in order to ensure all code uses the same instance.
 */
function ReduxQuerySync({
    store,
    params,
    replaceState,
    initialTruth,
    history = createHistory(),
}) {
    const { dispatch } = store

    // Two bits of state used to not respond to self-induced updates.
    let ignoreLocationUpdate = false
    let ignoreStateUpdate = false

    // Keeps the last seen values for comparing what has changed.
    let lastQueryValues

    // Helper function to support history module v3 as well as v4.
    function getLocation(history) {
        if (Object.hasOwnProperty.call(history, 'location')) {
            return history.location
        } else if (Object.hasOwnProperty.call(history, 'getCurrentLocation')) {
            return history.getCurrentLocation()
        }
    }

    function getQueryValues(location) {
        const locationParams = new URLSearchParams(location.search);
        const queryValues = {}
        Object.keys(params).forEach(param => {
            const { defaultValue, stringToValue = s => s } = params[param]
            const valueString = locationParams.get(param)
            const value = (valueString === null)
                ? defaultValue
                : stringToValue(valueString)
            queryValues[param] = value
        })
        return queryValues
    }

    function handleLocationUpdate(location) {
        // Support history v5
        if (location.location !== undefined) {
            location = location.location;
        }

        // Ignore the event if the location update was induced by ourselves.
        if (ignoreLocationUpdate) return

        const state = store.getState()

        // Read the values of the watched parameters
        const queryValues = getQueryValues(location)

        // For each parameter value that changed, we dispatch the corresponding action.
        const actionsToDispatch = []
        Object.keys(queryValues).forEach(param => {
            const value = queryValues[param]
            // Process the parameter both on initialisation and if it has changed since last time.
            // (should we just do this unconditionally?)
            if (lastQueryValues === undefined || lastQueryValues[param] !== value) {
                const { selector, action } = params[param]

                // Dispatch the action to update the state if needed.
                // (except on initialisation, this should always be needed)
                if (selector(state) !== value) {
                    actionsToDispatch.push(action(value))
                }
            }
        })

        lastQueryValues = queryValues

        ignoreStateUpdate = true
        actionsToDispatch.forEach(action => {
            dispatch(action)
        })
        ignoreStateUpdate = false

        // Update the location the again: reducers may have e.g. corrected invalid values.
        handleStateUpdate({replaceState: true})
    }

    function handleStateUpdate({replaceState}) {
        if (ignoreStateUpdate) return

        const state = store.getState()
        const location = getLocation(history)

        // Parse the current location's query string.
        const locationParams = new URLSearchParams(location.search);

        // Replace each configured parameter with its value in the state.
        Object.keys(params).forEach(param => {
            const { selector, defaultValue, valueToString = v => `${v}` } = params[param]
            const value = selector(state)
            if (value === defaultValue) {
                locationParams.delete(param)
            } else {
                locationParams.set(param, valueToString(value))
            }
            lastQueryValues[param] = value
        })
        const newLocationSearchString = `?${locationParams}`
        const oldLocationSearchString = location.search || '?'

        // Only update location if anything changed.
        if (newLocationSearchString !== oldLocationSearchString) {
            // Update location (but prevent triggering a state update).
            ignoreLocationUpdate = true
            const newLocation = {
                pathname: location.pathname,
                search: newLocationSearchString,
                hash: location.hash,
                state: location.state,
            }
            replaceState
                ? history.replace(newLocation)
                : history.push(newLocation)
            ignoreLocationUpdate = false
        }
    }

    // Sync location to store on every location change, and vice versa.
    const unsubscribeFromLocation = history.listen(handleLocationUpdate)
    const unsubscribeFromStore = store.subscribe(() => handleStateUpdate({replaceState}))

    // Sync location to store now, or vice versa, or neither.
    if (initialTruth === 'location') {
        handleLocationUpdate(getLocation(history))
    } else {
        // Just set the last seen values to later compare what changed.
        lastQueryValues = getQueryValues(getLocation(history))
    }
    if (initialTruth === 'store') {
        handleStateUpdate({replaceState: true})
    }

    return function unsubscribe() {
        unsubscribeFromLocation()
        unsubscribeFromStore()
    }
}

/**
 * For convenience, one can set up the synchronisation by passing this enhancer to createStore.
 *
 * @example
 *
 *     const storeEnhancer = ReduxQuerySync.enhancer({params, initialTruth: 'location'})
 *     const store = createStore(reducer, initialState, storeEnhancer)
 *
 * Arguments are equal to those of ReduxQuerySync itself, except that `store` can now be omitted.
 */
ReduxQuerySync.enhancer = function makeStoreEnhancer(config) {
    return storeCreator => (reducer, initialState, enhancer) => {
        // Create the store as usual.
        const store = storeCreator(reducer, initialState, enhancer)

        // Hook up our listeners.
        ReduxQuerySync({store, ...config})

        return store
    }
}

export default ReduxQuerySync
<|endoftext|>