<commit_before>var deserialize = require('./deserialize');
var serialize = require('./serialize');
var shell = require('./modes/shell');
var log = require('./modes/log');
var es = require('event-stream');
var JSONStream = require('JSONStream');
var raf = require('raf');
var format = require('util').format;

function preprocess(text, mode) {
  /* eslint indent:0 */
  mode = mode || 'strict';

  switch (mode) {
    case 'strict': return text;
    case 'shell': return shell.toStrict(text);
    case 'log': return log.toStrict(text);
    default:
      throw new Error(
        format('unknown mode `%s`. Use `strict` (default), `shell` or `log`.', mode)
      );
  }
}

/**
 * parses a string in strict, shell or log mode extended JSON and returns object with BSON values
 * @param  {String} text  string to parse
 * @param  {Function}     reviver callback function for custom parsing, @see ./reviver.js
 * @param  {Enum} mode    one of `strict`, `shell`, `log`
 * @return {Object}       Object with native and/or BSON values
 */
module.exports.parse = function(text, reviver, mode) {
  var normalized = preprocess(text, mode);
  var parsed = JSON.parse(normalized, reviver);
  return deserialize(parsed);
};

/**
 * stringifies an object with native and/or BSON values back into strict extended JSON
 * @param  {Object} value               Object or value to stringify
 * @param  {Function|Array} replacer    Custom replacement
 * @param  {Number|String} space        Custom spacing
 * @return {String}                     JSON representation of value
 *
 * @see http://mzl.la/1fms8sL  JSON.stringify() documentation
 */
module.exports.stringify = function(value, replacer, space) {
  return JSON.stringify(serialize(value), replacer, space);
};

module.exports.deserialize = deserialize;
module.exports.serialize = serialize;
module.exports.reviver = require('./reviver');

// Backwards compat for 1.x
module.exports.deflate = function() {
   /* eslint no-console:0 */
   console.warn('EJSON.deflate() has been deprecated in favor of EJSON.deserialize() '
     + 'and will be removed in the 2.x release.');
   return deserialize.apply(null, arguments);
 };

 module.exports.inflate = function() {
   /* eslint no-console:0 */
   console.warn('EJSON.inflate() has been deprecated in favor of EJSON.serialize() '
     + 'and will be removed in the 2.x release.');
   return serialize.apply(null, arguments);
 };

// JSONStream.stringify
module.exports.createStringifyStream = function(op, sep, cl, indent) {
  indent = indent || 0;
  if (op === false) {
    op = '';
    sep = '\n';
    cl = '';
  } else if (op === null || op === undefined) {
    op = '[\n';
    sep = '\n,\n';
    cl = '\n]\n';
  }

  // else, what ever you like

  var first = true;
  var anyData = false;

  return es.through(function(data) {
    anyData = true;
    var json = module.exports.stringify(data, null, indent);
    if (first) {
      first = false;
      this.emit('data', op + json);
    } else {
      this.emit('data', sep + json);
    }
  }, function() {
    if (!anyData) {
      this.emit('data', op);
    }
    this.emit('data', cl);
    this.emit('end');
  });
};

module.exports.createParseStream = function(path, map) {
  var parser = JSONStream.parse(path, map);
  var wrapper = es.through(function(data) {
    raf(function() {
      parser.write(data);
    });
  }, function() {
    this.emit('end');
  });

  var emit = function ejsonParseEmit(data) {
    wrapper.emit('data', data);
  };

  parser.on('data', function(data) {
    deserialize.async(data, function(_, parsed) {
      if (!Array.isArray(parsed)) {
        emit(parsed);
      } else {
        for (var i = 0; i < parsed.length; i++) {
          emit(parsed[i]);
        }
      }
    });
  })
    .on('error', function(err) {
      wrapper.emit('error', err);
    })
    .on('end', function() {
      wrapper.emit('end');
    });
  return wrapper;
};
<commit_msg>:bug: use `util.deprecate`<commit_after>var deserialize = require('./deserialize');
var serialize = require('./serialize');
var shell = require('./modes/shell');
var log = require('./modes/log');
var es = require('event-stream');
var JSONStream = require('JSONStream');
var raf = require('raf');
var util = require('util');
var format = util.format;
var deprecate = util.deprecate;

function preprocess(text, mode) {
  /* eslint indent:0 */
  mode = mode || 'strict';

  switch (mode) {
    case 'strict': return text;
    case 'shell': return shell.toStrict(text);
    case 'log': return log.toStrict(text);
    default:
      throw new Error(
        format('unknown mode `%s`. Use `strict` (default), `shell` or `log`.', mode)
      );
  }
}

/**
 * parses a string in strict, shell or log mode extended JSON and returns object with BSON values
 * @param  {String} text  string to parse
 * @param  {Function}     reviver callback function for custom parsing, @see ./reviver.js
 * @param  {Enum} mode    one of `strict`, `shell`, `log`
 * @return {Object}       Object with native and/or BSON values
 */
module.exports.parse = function(text, reviver, mode) {
  var normalized = preprocess(text, mode);
  var parsed = JSON.parse(normalized, reviver);
  return deserialize(parsed);
};

/**
 * stringifies an object with native and/or BSON values back into strict extended JSON
 * @param  {Object} value               Object or value to stringify
 * @param  {Function|Array} replacer    Custom replacement
 * @param  {Number|String} space        Custom spacing
 * @return {String}                     JSON representation of value
 *
 * @see http://mzl.la/1fms8sL  JSON.stringify() documentation
 */
module.exports.stringify = function(value, replacer, space) {
  return JSON.stringify(serialize(value), replacer, space);
};

module.exports.deserialize = deserialize;
module.exports.serialize = serialize;
module.exports.reviver = require('./reviver');

// Backwards compat for 1.x
module.exports.deflate = deprecate(deserialize,
  'mongodb-extended-json#deflate: Use deserialize(obj) instead');

module.exports.inflate = deprecate(serialize,
  'mongodb-extended-json#inflate: Use serialize(obj) instead');

// JSONStream.stringify
module.exports.createStringifyStream = function(op, sep, cl, indent) {
  indent = indent || 0;
  if (op === false) {
    op = '';
    sep = '\n';
    cl = '';
  } else if (op === null || op === undefined) {
    op = '[\n';
    sep = '\n,\n';
    cl = '\n]\n';
  }

  // else, what ever you like

  var first = true;
  var anyData = false;

  return es.through(function(data) {
    anyData = true;
    var json = module.exports.stringify(data, null, indent);
    if (first) {
      first = false;
      this.emit('data', op + json);
    } else {
      this.emit('data', sep + json);
    }
  }, function() {
    if (!anyData) {
      this.emit('data', op);
    }
    this.emit('data', cl);
    this.emit('end');
  });
};

module.exports.createParseStream = function(path, map) {
  var parser = JSONStream.parse(path, map);
  var wrapper = es.through(function(data) {
    raf(function() {
      parser.write(data);
    });
  }, function() {
    this.emit('end');
  });

  var emit = function ejsonParseEmit(data) {
    wrapper.emit('data', data);
  };

  parser.on('data', function(data) {
    deserialize.async(data, function(_, parsed) {
      if (!Array.isArray(parsed)) {
        emit(parsed);
      } else {
        for (var i = 0; i < parsed.length; i++) {
          emit(parsed[i]);
        }
      }
    });
  })
    .on('error', function(err) {
      wrapper.emit('error', err);
    })
    .on('end', function() {
      wrapper.emit('end');
    });
  return wrapper;
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var cancellableNextTick = require('cancellable-next-tick');
var querystring = require('querystring');
var getRouteParser = require('./util/getRouteParser');

if (process.browser){
    var History = require('html5-history');
    var routers = [];
    History.Adapter.bind(window, 'statechange', function(){
        _.forEach(routers, function(router){
            router._update(new router.Route(window.document.location.pathname + window.document.location.search));
        });
    });
}

var Route = function(router, a, b){
    var self = this;
    if (b){
        self.name = a;
        self.params = b;
        var route_parser = getRouteParser(router.patterns[self.name], self._cacheRouteParsers);
        var pathname = route_parser.reverse(self.params);
        if (pathname===false){throw new Error('Missing required parameter(s) for route');}
        _.find(router.patterns, function(pattern, _name){
            if (_name == self.name){return true;}
            if (getRouteParser(router.patterns[_name], self._cacheRouteParsers).match(pathname)) {
                throw new Error('Found unreachable route. Url "' + pathname + '" (for route "' + self.name
                    + '" and params ' + JSON.stringify(self.params) + ') matches route "' + _name
                    + '" first! Try changing the order of the patterns');
            }
        });
        var pathname_params = route_parser.match(pathname);
        var query_params = {};
        _.forEach(self.params, function(value, key){
            if (!(key in pathname_params)){
                query_params[key] = value;
            }
        });
        self.url =  pathname + (JSON.stringify(query_params)=='{}' ? '' : '?' + querystring.encode(query_params));
    } else {
        self.url = a;
        self.name = null;
        self.params = null;
        _.find(router.patterns, function(pattern, _name){
            var pathname_params = getRouteParser(pattern, self._cacheRouteParsers).match(self.url);
            if (pathname_params){
                var index = self.url.indexOf('?');
                var query_params = index == -1 ? {} : querystring.decode(self.url.slice(index+1));
                self.name = _name;
                self.params = _.assign({}, query_params, pathname_params);
                return true;
            }
        });
    }
};
Route.prototype.equals = function(route){
    var self = this;
    return (self.name==route.name) && _.isEqual(self.params, route.params);
};

var ObsRouter = function(patterns, options){
    var self = this;
    if (!(typeof patterns=='object')){throw new Error('ObsRouter constructor expects patterns object as first argument')}
    self.patterns = patterns;
    options = _.assign({}, ObsRouter.defaults, options || {});
    self._cacheRouteParsers = options.cacheRouteParsers;
    self._bindToWindow = process.browser ? options.bindToWindow : false;
    if (self._bindToWindow){
        // add to module-scoped list of routers
        routers.push(self);
        // override any url input with window location
        options.url = window.document.location.pathname + window.document.location.search;
    }
    // create Route class for this Router instance
    var Super = self.Route;
    self.Route = function(a, b){
        if (!(this instanceof self.Route)){
            return new self.Route(a, b)
        }
        Super.call(this, self, a, b);
    };
    self.Route.prototype = _.create(Super.prototype);
    // initialise rest of public instance members (route & old_route)
    self.old_route = new self.Route('');
    self.route = new self.Route(options.url);
    // implement initialEmit option
    if (options.initialEmit){
        self._cancel_initial_emit = cancellableNextTick(function(){
            self._emit();
        });
    }
};

ObsRouter.defaults = {
    url: '',
    cacheRouteParsers: true,
    bindToWindow: true,
    initialEmit: true
};

ObsRouter.prototype = _.create(EventEmitter.prototype);

/**
 * Cleanup method to be called when you're done with your ObsRouter instance.
 */
ObsRouter.prototype.destroy = function(){
    this.removeAllListeners();
    if (this._bindToWindow){
        routers = _.without(routers, this);
    }
    if (self._cancel_initial_emit){
        self._cancel_initial_emit()
        delete self._cancel_initial_emit;
    }
};
ObsRouter.prototype.back = function(n){
    this.go(-(n || 1));
};
ObsRouter.prototype.forward = function(n){
    this.go(n || 1);
};
ObsRouter.prototype.go = function(n){
    if (this._bindToWindow){
        process.nextTick(function(){
            History.go(n);
        });
    } else {
        console.warn('ObsRouter#back(), ObsRouter#forward(), and ObsRouter#go() are not implemented ' +
            'for server and only work on browser with bindToWindow option');
    }
};
ObsRouter.prototype.replaceUrl = function(url){
    this._change(new this.Route(url), 'replace');
};
ObsRouter.prototype.replaceRoute = function(a, b){
    this._change(this._resolveRoute(a, b), 'replace');
};
ObsRouter.prototype.replaceParams = function(params, extend){
    this._change(this._resolveParams(params, extend), 'replace')
};
ObsRouter.prototype.pushUrl = function(url){
    this._change(new this.Route(url), 'push')
};
ObsRouter.prototype.pushRoute = function(a, b){
    this._change(this._resolveRoute(a, b), 'push');
};
ObsRouter.prototype.pushParams = function(params, extend){
    this._change(this._resolveParams(params, extend), 'push');
};
ObsRouter.prototype.Route = Route;
ObsRouter.prototype._change = function(route, mode){
    var self = this;
    if (self.route.equals(route)){return;}
    if (self._cancel_initial_emit){
        self._cancel_initial_emit()
        delete self._cancel_initial_emit;
    }
    if (self._bindToWindow){
        switch (mode){
            case 'replace':
                process.nextTick(function() {
                    History.replaceState({}, window.document.title, route.url);
                });
                return;
            case 'push':
                process.nextTick(function() {
                    History.pushState({}, window.document.title, route.url);
                });
                return;
        }
    } else {
        switch (mode){
            case 'replace':
            case 'push':
                process.nextTick(function(){
                    self._update(route);
                });
                return;
        }
    }
    throw new Error('Unrecognized update mode \''+mode+'\'!');
};
ObsRouter.prototype._update = function(route){
    var self = this;
    if (self.route.equals(route)){return console.log('self.route.equals(route)');}
    self.old_route = self.route;
    self.route = route;
    self._emit();
};

ObsRouter.prototype._emit = function(){
    var self = this;
    self.emit('route', self.route, self.old_route);

};
ObsRouter.prototype._resolveRoute = function(a, b){
    var self = this;
    var route;
    if ((typeof a=='object') && (a instanceof self.Route)){
        route = a;
    } else if (_.isObject(a) && _.isString(a.name) && _.isObject(a.params)) {
        route = new self.Route(a.name, a.params);
    } else if ((typeof a=='string') && (!b || (typeof b=='object'))) {
        route = new self.Route(a, b || {});
    } else {
        throw new Error('Expected \'string\' for route name and \'object\' for route parameters (or instance of '
            + 'ObsRouter#Route or other object with \'name\' and \'params\' properties) \r\n'
            + 'Instead received \''+(typeof a)+'\' and \''+(typeof b)+'\'')
    }
    return route;
};
ObsRouter.prototype._resolveParams = function(params, extend){
    var self = this;
    return new self.Route(self.route.name, _.assign({}, extend ? self.route.params : {}, params));
};

module.exports = ObsRouter;
<commit_msg>safer Route#equals<commit_after>var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var cancellableNextTick = require('cancellable-next-tick');
var querystring = require('querystring');
var getRouteParser = require('./util/getRouteParser');

if (process.browser){
    var History = require('html5-history');
    var routers = [];
    History.Adapter.bind(window, 'statechange', function(){
        _.forEach(routers, function(router){
            router._update(new router.Route(window.document.location.pathname + window.document.location.search));
        });
    });
}

var Route = function(router, a, b){
    var self = this;
    if (b){
        self.name = a;
        self.params = b;
        var route_parser = getRouteParser(router.patterns[self.name], self._cacheRouteParsers);
        var pathname = route_parser.reverse(self.params);
        if (pathname===false){throw new Error('Missing required parameter(s) for route');}
        _.find(router.patterns, function(pattern, _name){
            if (_name == self.name){return true;}
            if (getRouteParser(router.patterns[_name], self._cacheRouteParsers).match(pathname)) {
                throw new Error('Found unreachable route. Url "' + pathname + '" (for route "' + self.name
                    + '" and params ' + JSON.stringify(self.params) + ') matches route "' + _name
                    + '" first! Try changing the order of the patterns');
            }
        });
        var pathname_params = route_parser.match(pathname);
        var query_params = {};
        _.forEach(self.params, function(value, key){
            if (!(key in pathname_params)){
                query_params[key] = value;
            }
        });
        self.url =  pathname + (JSON.stringify(query_params)=='{}' ? '' : '?' + querystring.encode(query_params));
    } else {
        self.url = a;
        self.name = null;
        self.params = null;
        _.find(router.patterns, function(pattern, _name){
            var pathname_params = getRouteParser(pattern, self._cacheRouteParsers).match(self.url);
            if (pathname_params){
                var index = self.url.indexOf('?');
                var query_params = index == -1 ? {} : querystring.decode(self.url.slice(index+1));
                self.name = _name;
                self.params = _.assign({}, query_params, pathname_params);
                return true;
            }
        });
    }
};
Route.prototype.equals = function(route){
    var self = this;
    return _.isObject(route) && (route.name==self.name) && _.isEqual(route.params, self.params);
};

var ObsRouter = function(patterns, options){
    var self = this;
    if (!(typeof patterns=='object')){throw new Error('ObsRouter constructor expects patterns object as first argument')}
    self.patterns = patterns;
    options = _.assign({}, ObsRouter.defaults, options || {});
    self._cacheRouteParsers = options.cacheRouteParsers;
    self._bindToWindow = process.browser ? options.bindToWindow : false;
    if (self._bindToWindow){
        // add to module-scoped list of routers
        routers.push(self);
        // override any url input with window location
        options.url = window.document.location.pathname + window.document.location.search;
    }
    // create Route class for this Router instance
    var Super = self.Route;
    self.Route = function(a, b){
        if (!(this instanceof self.Route)){
            return new self.Route(a, b)
        }
        Super.call(this, self, a, b);
    };
    self.Route.prototype = _.create(Super.prototype);
    // initialise rest of public instance members (route & old_route)
    self.old_route = new self.Route('');
    self.route = new self.Route(options.url);
    // implement initialEmit option
    if (options.initialEmit){
        self._cancel_initial_emit = cancellableNextTick(function(){
            self._emit();
        });
    }
};

ObsRouter.defaults = {
    url: '',
    cacheRouteParsers: true,
    bindToWindow: true,
    initialEmit: true
};

ObsRouter.prototype = _.create(EventEmitter.prototype);

/**
 * Cleanup method to be called when you're done with your ObsRouter instance.
 */
ObsRouter.prototype.destroy = function(){
    this.removeAllListeners();
    if (this._bindToWindow){
        routers = _.without(routers, this);
    }
    if (self._cancel_initial_emit){
        self._cancel_initial_emit()
        delete self._cancel_initial_emit;
    }
};
ObsRouter.prototype.back = function(n){
    this.go(-(n || 1));
};
ObsRouter.prototype.forward = function(n){
    this.go(n || 1);
};
ObsRouter.prototype.go = function(n){
    if (this._bindToWindow){
        process.nextTick(function(){
            History.go(n);
        });
    } else {
        console.warn('ObsRouter#back(), ObsRouter#forward(), and ObsRouter#go() are not implemented ' +
            'for server and only work on browser with bindToWindow option');
    }
};
ObsRouter.prototype.replaceUrl = function(url){
    this._change(new this.Route(url), 'replace');
};
ObsRouter.prototype.replaceRoute = function(a, b){
    this._change(this._resolveRoute(a, b), 'replace');
};
ObsRouter.prototype.replaceParams = function(params, extend){
    this._change(this._resolveParams(params, extend), 'replace')
};
ObsRouter.prototype.pushUrl = function(url){
    this._change(new this.Route(url), 'push')
};
ObsRouter.prototype.pushRoute = function(a, b){
    this._change(this._resolveRoute(a, b), 'push');
};
ObsRouter.prototype.pushParams = function(params, extend){
    this._change(this._resolveParams(params, extend), 'push');
};
ObsRouter.prototype.Route = Route;
ObsRouter.prototype._change = function(route, mode){
    var self = this;
    if (self.route.equals(route)){return;}
    if (self._cancel_initial_emit){
        self._cancel_initial_emit()
        delete self._cancel_initial_emit;
    }
    if (self._bindToWindow){
        switch (mode){
            case 'replace':
                process.nextTick(function() {
                    History.replaceState({}, window.document.title, route.url);
                });
                return;
            case 'push':
                process.nextTick(function() {
                    History.pushState({}, window.document.title, route.url);
                });
                return;
        }
    } else {
        switch (mode){
            case 'replace':
            case 'push':
                process.nextTick(function(){
                    self._update(route);
                });
                return;
        }
    }
    throw new Error('Unrecognized update mode \''+mode+'\'!');
};
ObsRouter.prototype._update = function(route){
    var self = this;
    if (self.route.equals(route)){return console.log('self.route.equals(route)');}
    self.old_route = self.route;
    self.route = route;
    self._emit();
};

ObsRouter.prototype._emit = function(){
    var self = this;
    self.emit('route', self.route, self.old_route);

};
ObsRouter.prototype._resolveRoute = function(a, b){
    var self = this;
    var route;
    if ((typeof a=='object') && (a instanceof self.Route)){
        route = a;
    } else if (_.isObject(a) && _.isString(a.name) && _.isObject(a.params)) {
        route = new self.Route(a.name, a.params);
    } else if ((typeof a=='string') && (!b || (typeof b=='object'))) {
        route = new self.Route(a, b || {});
    } else {
        throw new Error('Expected \'string\' for route name and \'object\' for route parameters (or instance of '
            + 'ObsRouter#Route or other object with \'name\' and \'params\' properties) \r\n'
            + 'Instead received \''+(typeof a)+'\' and \''+(typeof b)+'\'')
    }
    return route;
};
ObsRouter.prototype._resolveParams = function(params, extend){
    var self = this;
    return new self.Route(self.route.name, _.assign({}, extend ? self.route.params : {}, params));
};

module.exports = ObsRouter;
<|endoftext|>


<commit_before>/* global analytics */
/* eslint no-console:0, no-unused-vars:0, no-extra-parens:0 */

'use strict';

var integration = require('analytics.js-integration');
var request = require('visionmedia/superagent');
var prefix = require('johntron/superagent-prefix');
var async = require('caolan/async');
var uuid = require('uuid');
window._astq = window._astq || [];

/**
 * Expose `Astronomer` integration.
 */
var Astronomer = module.exports = exports = integration('astronomer')
  .global('_astq')
  .option('appId', null)
  .option('credentialServer', 'https://app.astronomer.io:443')
  .option('credentialsExpiration', 900)
  .option('trackAllPages', false)
  .option('trackNamedPages', true)
  .option('trackCategorizedPages', true)
  .tag('aws', '<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.33.min.js">');

/**
 * Initialize astronomer.
 * @param {Facade} page The page object
 */
Astronomer.prototype.initialize = function() {
  var self = this;
  // Hold user data
  self.props = {};
  // The kinesis service object
  self.kinesis = null;
  // Need concurrency 1 here to prevent multiple auth requests simultaneously
  self.queue = async.queue(self.putRecord.bind(self), 1);
  // Load aws sdk, then signal ready
  self.load('aws', function() {
    self.ready();
    self.replay();
  });
};

/**
 * Authenticate with our server
 * @param {Function} callback Callback
 */
Astronomer.prototype.ensureKinesisConfig = function(callback) {
  var self = this;

  if (!self.expired()) {
    callback(null, { config: self.config, kinesis: self.kinesis });
    return;
  }

  // Request fresh config (token)
  self.requestConfig(function(err, response) {
    if (err) { return callback(err); }

    // Update global config object
    self.config = response.body;

    var params = {
      region: self.config.region,
      credentials: new window.AWS.WebIdentityCredentials({
        RoleArn: self.config.roleArn,
        WebIdentityToken: self.config.credentials.Token,
        DurationSeconds: self.options.credentialsExpiration
      })
    };

    // Assign kinesis service object to new one with fresh config
    self.kinesis = new window.AWS.Kinesis(params);

    callback(null, { config: self.config, kinesis: self.kinesis });
  });
};

/**
 * Check credentials on kinesis service object
 * @return {Boolean} If expired
 */
Astronomer.prototype.expired = function() {
  var expireTime = (((this.kinesis || {}).config || {}).credentials || {}).expireTime;
  return !expireTime || expireTime.getTime() <= Date.now();
};

/**
 * Worker handler function
 * @param {Object} record Record to put to kinesis
 * @param {Function} callback Function to callback to queue
 */
Astronomer.prototype.putRecord = function(record, callback) {
  var self = this;
  self.ensureKinesisConfig(function(err, config) {
    if (err) {
      console.error('Error retrieving config.');
      return callback();
    }

    var params = {
      Data: JSON.stringify(record),
      StreamName: config.config.streamName,
      PartitionKey: self.options.appId
    };

    config.kinesis.putRecord(params, function(err, data) {
      if (err) {
        console.log(err);
      }
      callback();
    });
  });
};

/**
 * Request new configuration from server, including fresh token
 * @param {Function} callback Callback passing in response
 */
Astronomer.prototype.requestConfig = function(callback) {
  request.get('/api/v1/applications/credentials/' + this.options.appId)
    .use(prefix(this.options.credentialServer))
    .end(callback);
};

/**
 * Replay the events that have been queued prior to initialization
 */
Astronomer.prototype.replay = function() {
  while (window._astq.length > 0) {
    var item = window._astq.shift();
    var method = item.shift();
    if (analytics[method]) analytics[method].apply(analytics, item);
  }
};

/**
 * Has the astronomer library been loaded yet?
 * @return {Boolean}
 */
Astronomer.prototype.loaded = function() {
  return !!((((window.AWS) || {}).config || {}).credentials || {}).accessKeyId;
};

/**
 * Trigger a page view.
 * @param {Facade} page A page object
 */
Astronomer.prototype.page = function(page) {
  this.push(page.json());
};

/**
 * Identify a user.
 * @param {Facade} identify An identify object
 */
Astronomer.prototype.identify = function(identify) {
  this.push(identify.json());
};

/**
 * Associate the current user with a group of users.
 * @param {Facade} group A group object
 */
Astronomer.prototype.group = function(group) {
  this.push(group.json());
};

/**
 * Track an event.
 * @param {Facade} track A track object
 */
Astronomer.prototype.track = function(track) {
  this.push(track.json());
};

/**
 * Push a message to the outgoing queue.
 * @param {Object} msg
 */
Astronomer.prototype.push = function(msg) {
  msg = this.normalize(msg);
  this.queue.push(msg);
};

/**
 * Normalize a given msg.
 * @param {Object} msg
 */
Astronomer.prototype.normalize = function(msg) {
  var user = this.analytics.user();
  msg.userId = msg.userId || user.id();
  msg.anonymousId = user.anonymousId();
  msg.messageId = uuid();
  msg.sentAt = new Date();
  return msg;
};<commit_msg>SAVEPOINT<commit_after>/* global analytics */
/* eslint no-console:0, no-unused-vars:0, no-extra-parens:0 */

'use strict';

var integration = require('analytics.js-integration');
var request = require('visionmedia/superagent');
var prefix = require('johntron/superagent-prefix');
var async = require('caolan/async');
var uuid = require('uuid');
window._astq = window._astq || [];

/**
 * Expose `Astronomer` integration.
 */
var Astronomer = module.exports = exports = integration('astronomer')
  .global('_astq')
  .option('appId', null)
  .option('credentialServer', 'https://app.astronomer.io:443')
  .option('credentialsExpiration', 900)
  .option('trackAllPages', false)
  .option('trackNamedPages', true)
  .option('trackCategorizedPages', true)
  .tag('aws', '<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.33.min.js">');

/**
 * Initialize astronomer.
 * @param {Facade} page The page object
 */
Astronomer.prototype.initialize = function() {
  var self = this;
  // Hold user data
  self.props = {};
  // The kinesis service object
  self.kinesis = null;
  // Need concurrency 1 here to prevent multiple auth requests simultaneously
  self.queue = async.queue(self.putRecord.bind(self), 1);
  // Load aws sdk, then signal ready
  self.load('aws', function() {
    self.ready();
    self.replay();
  });
};

/**
 * Authenticate with our server
 * @param {Function} callback Callback
 */
Astronomer.prototype.ensureKinesisConfig = function(callback) {
  var self = this;

  if (!self.expired()) {
    callback(null, { config: self.config, kinesis: self.kinesis });
    return;
  }

  // Request fresh config (token)
  self.requestConfig(function(err, response) {
    if (err) { return callback(err); }

    // Update global config object
    self.config = response.body;

    var params = {
      region: self.config.region,
      credentials: new window.AWS.WebIdentityCredentials({
        RoleArn: self.config.roleArn,
        WebIdentityToken: self.config.credentials.Token,
        DurationSeconds: self.options.credentialsExpiration
      })
    };

    // Assign kinesis service object to new one with fresh config
    self.kinesis = new window.AWS.Kinesis(params);

    callback(null, { config: self.config, kinesis: self.kinesis });
  });
};

/**
 * Check credentials on kinesis service object
 * @return {Boolean} If expired
 */
Astronomer.prototype.expired = function() {
  var expireTime = (((this.kinesis || {}).config || {}).credentials || {}).expireTime;
  return !expireTime || expireTime.getTime() <= Date.now();
};

/**
 * Worker handler function
 * @param {Object} record Record to put to kinesis
 * @param {Function} callback Function to callback to queue
 */
Astronomer.prototype.putRecord = function(record, callback) {
  var self = this;
  self.ensureKinesisConfig(function(err, config) {
    if (err) {
      console.error('Error retrieving config.');
      return callback();
    }

    var params = {
      Data: JSON.stringify(record),
      StreamName: config.config.streamName,
      PartitionKey: self.options.appId
    };

    config.kinesis.putRecord(params, function(err, data) {
      if (err) {
        console.log(err);
      }
      callback();
    });
  });
};

/**
 * Request new configuration from server, including fresh token
 * @param {Function} callback Callback passing in response
 */
Astronomer.prototype.requestConfig = function(callback) {
  request.get('/api/v1/applications/credentials/' + this.options.appId)
    .use(prefix(this.options.credentialServer))
    .end(callback);
};

/**
 * Replay the events that have been queued prior to initialization
 */
Astronomer.prototype.replay = function() {
  while (window._astq.length > 0) {
    var item = window._astq.shift();
    var method = item.shift();
    if (analytics[method]) analytics[method].apply(analytics, item);
  }
};

/**
 * Has the astronomer library been loaded yet?
 * @return {Boolean}
 */
Astronomer.prototype.loaded = function() {
  return !!((((window.AWS) || {}).config || {}).credentials || {}).accessKeyId;
};

/**
 * Trigger a page view.
 * @param {Facade} page A page object
 */
Astronomer.prototype.page = function(page) {
  this.push(page.json());
};

/**
 * Identify a user.
 * @param {Facade} identify An identify object
 */
Astronomer.prototype.identify = function(identify) {
  this.push(identify.json());
};

/**
 * Associate the current user with a group of users.
 * @param {Facade} group A group object
 */
Astronomer.prototype.group = function(group) {
  this.push(group.json());
};

/**
 * Track an event.
 * @param {Facade} track A track object
 */
Astronomer.prototype.track = function(track) {
  this.push(track.json());
};

/**
 * Alais a user.
 * @param {Alias} alias An alias object
 */
Astronomer.prototype.alias = function(alias) {
  var json = alias.json();
  var user = this.analytics.user();
  json.previousId = json.previousId || json.from || user.id() || user.anonymousId();
  json.userId = json.userId || json.to;
  delete json.from;
  delete json.to;
  this.push(json);
};

/**
 * Push a message to the outgoing queue.
 * @param {Object} msg JSON
 */
Astronomer.prototype.push = function(msg) {
  msg = this.normalize(msg);
  this.queue.push(msg);
};

/**
 * Normalize a given msg.
 * @param {Object} msg JSON
 * @return {Object} msg Normalized JSON
 */
Astronomer.prototype.normalize = function(msg) {
  var user = this.analytics.user();
  msg.userId = msg.userId || user.id();
  msg.anonymousId = user.anonymousId();
  msg.messageId = uuid();
  msg.sentAt = new Date();
  return msg;
};
<|endoftext|>


<commit_before>/* @flow */
import fs from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
import fastDiff from 'fast-diff';
import pGlob from 'glob-promise';
import pMap from 'p-map';
import matter from 'gray-matter';
import zipObject from 'lodash.zipobject';
import uniq from 'lodash.uniq';
import pkgDir from 'pkg-dir';
import toml from 'toml';
import aru from 'aru';
import {readonly} from 'core-decorators';
import compiler from './compiler';
import Section from './section';
import Server from './server';
import View from './view';
import {Compiled} from './compiler';

export interface Content {
  code: string;
  comparison: Compiled;
}
export type YosugaTarget = 'css' | 'less' | 'sass' | 'stylus' | 'postcss';
export type YosugaTargetDirs = {[target: YosugaTarget]: string};
export interface YosugaOptions {
  // origin: string;
  name: string;
  port: number;
  main: YosugaTarget;
  base: string;
  targets: YosugaTarget[];
  targetDirs: YosugaTargetDirs;
  style: {
    fontSize?: string,
    accentColor?: string,
  };
}

// ¯\_(ツ)_/¯
export type YosugaDefaultOptions = $Diff<
  YosugaOptions & {
    base: string,
    targets: any,
    style: {
      fontSize?: string,
      accentColor?: string,
    },
  },
  YosugaOptions,
>;

export interface YosugaServerOptions {
  port: number;
  watch({srcDir: string}): string;
  force: boolean;
}

export default class Yosuga {
  sections: Section[];

  /**
   * Server used for development
   */
  server: Server;

  /**
   * Mainly Nuxt
   */
  view: View;

  /**
   * Option for Yosuga
   */
  opts: YosugaOptions;

  /**
   * Theme builder using Postcss
   */
  // $css: any;

  /**
   * Whether the server is built
   */
  served: boolean;

  /**
   * Directory paths
   * @prop lib Dirname for nuxt/lib
   * @prop data Filename for nuxt/lib/data.json
   * @prop opts Filename for nuxt/lib/opts.json
   * @prop style Dirname for nuxt/static/yosuga.css
   */
  _filepath: {
    lib: string,
    data: string,
    opts: string,
    style: string,
  };

  /**
   * Yosuga's default options
   */
  @readonly
  static defaultOpts: YosugaDefaultOptions = {
    name: '',
    port: 3333,
    main: 'css',
    targetDirs: {
      yosuga: 'yosuga',
      css: 'css',
      postcss: 'postcss',
      sass: 'sass',
      less: 'less',
      stylus: 'stylus',
    },
    style: {
      fontSize: '14px',
      accentColor: '#cb1b45',
    },
  };

  constructor(opts: any = Yosuga.defaultOpts) {
    this.sections = [];
    this.view = new View(opts);
    this.opts = {...Yosuga.defaultOpts, ...opts};
    this.opts.style = {
      ...(Yosuga.defaultOpts: any).style,
      ...(opts.style || {}),
    };
    this._filepath = {
      lib: path.join(__dirname, '../lib/view/lib/'),
      data: path.join(__dirname, '../lib/view/lib/data.json'),
      opts: path.join(__dirname, '../lib/view/lib/opts.json'),
      style: path.join(__dirname, '../lib/view/static/yosuga.css'),
    };
  }

  /**
   * Determine whether the target tool exists directory
   */
  async _getTargets(): Promise<YosugaTarget[]> {
    const result: YosugaTarget[] = [];
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.css),
      )
    ) {
      result.push('css');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.postcss),
      )
    ) {
      result.push('postcss');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.sass),
      )
    ) {
      result.push('sass');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.less),
      )
    ) {
      result.push('less');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.stylus),
      )
    ) {
      result.push('stylus');
    }

    return result;
  }

  async _getTargetContents(
    compiler,
    targets: YosugaTarget[],
  ): {[target: YosugaTarget]: {[filename: string]: Content}} {
    const items = await pMap(targets, async target => {
      const targetDirname = path.resolve(this.opts.base, target);

      const pattern = path.join(targetDirname, '**/*.*');
      const comparisonPattern = path.join(targetDirname, '**/*.yosuga.*');
      const filenames = await pGlob(pattern);
      const comparisonFilenames = await pGlob(comparisonPattern);
      const basenames = filenames
        .filter(filename => !filename.includes('.yosuga.'))
        .map(filename => {
          const extname = path.extname(filename);
          return path.basename(filename, extname);
        });

      const contents = await pMap(filenames, async filename => {
        const code = await fs.readFile(filename, 'utf-8');
        let css = code;
        if (path.extname(filename) === '.css') {
          css = await compiler.standardize(code);
        }
        return {
          code,
          comparison: {css},
        };
      });
      const map = zipObject(basenames, contents);

      await pMap(comparisonFilenames, async filename => {
        const extname = path.extname(filename);
        const key = path.basename(filename, `.yosuga${extname}`);
        map[key].altCode = await fs.readFile(filename, 'utf-8');
        map[key].comparison = await compiler[extname](filename, 'output.css');

        // eslint-disable-next-line no-warning-comments
        // TODO: sourcemap merge
        map[key].comparison.css = await compiler.standardize(
          map[key].comparison.css,
        );
      });

      return map;
    });

    const readyState = zipObject(targets, items);
    // section names
    // e.g.) [ 'button', 'card', 'nav', 'table' ]
    const targetSectionKeys = Object.keys(readyState[this.opts.main]);

    const result = targetSectionKeys.every(key => {
      const items: any = this._getComparisonBy(readyState, key);
      const valid = this._diff(items, key);
      if (!valid) {
        for (const content of (Object.values(readyState): any)) {
          delete content[key];
        }
      }
      return valid;
    });

    if (!result) {
      throw new Error(
        chalk.red('ERR! There are sections where each is not equal'),
      );
    }

    return readyState;
  }

  _getComparisonBy(
    map: {[target: YosugaTarget]: {[filename: string]: Content}},
    key: string,
  ): Compiled[] {
    const groups: any = Object.entries(map);
    return groups
      .map(([target, items]: [YosugaTarget, {[filename: string]: Content}]) => {
        const comparison = (items[key] || {}).comparison;
        if (comparison === undefined) {
          return undefined;
        }

        (comparison: any).target = target;
        return comparison;
      })
      .filter(comparison => comparison);
  }

  _diff(
    comparisonItems: (Compiled & {target: YosugaTarget})[],
    key: string,
  ): boolean {
    const baseItem = comparisonItems[0];

    return comparisonItems.slice(1).every(item => {
      const diffed = fastDiff(baseItem.css, item.css);

      if (diffed.length === 1) {
        return true;
      }

      const result = diffed
        .map(chunks => {
          switch (chunks[0]) {
            case -1: {
              return chalk.green(chunks[1]);
            }
            case 0:
            default: {
              return chunks[1];
            }
            case 1: {
              return chalk.red(chunks[1]);
            }
          }
        })
        .join('');

      if (process.env.NODE_ENV !== 'test') {
        console.log(chalk.bold(`${chalk.yellow('`' + key + '`')} is missing`));
        console.log(
          chalk.bold(
            `${chalk.gray(' - ')}${chalk.green(baseItem.target)}${chalk.gray(
              ' / ',
            )}${chalk.red(item.target)}`,
          ),
        );
        console.log();
        console.log(result);
        console.log();
        console.log();
        console.log();
      }
      return false;
    });
  }

  async _getSections(basenames: string[]): Promise<Section[]> {
    // eslint-disable-next-line no-return-await
    return await pMap(basenames, async name => {
      const filePattern = path.join(
        this.opts.base,
        (this.opts.targetDirs: any).yosuga,
        `*${name}.html`,
      );
      const [filename] = await pGlob(filePattern);
      if (!filename) {
        throw new Error(`ERR! ${name}.html was not found`);
      }
      const content = await fs.readFile(filename, 'utf-8');
      const result = matter(content, {
        language: 'toml',
        engines: {
          toml: {
            parse: toml.parse.bind(toml),
          },
        },
      });
      const parsed = path.parse(filename);
      parsed.base = parsed.base.replace(/^\d+[.:|]/, '');
      result.filename = path.format((parsed: any));

      return new Section(name, result);
    });
  }

  async prepare(): Promise<void> {
    if (this.opts.name === '') {
      const pkgDirname = await pkgDir(process.cwd());
      try {
        const pkg = await fs.readJson(path.join(pkgDirname, 'package.json'));
        this.opts.name = pkg.name;
      } catch (_) {
        // default
        this.opts.name = 'Yosuga';
      }
    }

    if (this.opts.base === undefined) {
      this.opts.base = await pkgDir(process.cwd());
    }

    let targets = await this._getTargets();
    targets = uniq([this.opts.main, ...targets]);
    const targetContents = await this._getTargetContents(compiler, targets);
    const validKeys = Object.keys(targetContents[this.opts.main]);

    if (validKeys.length === 0) {
      throw new Error('ERR! There is no valid item');
    }

    this.sections = await (this: any)._getSections(validKeys);

    this.sections.forEach(section => {
      section.items = targets
        .map(target => {
          if (target === this.opts.main) {
            section.css = targetContents[target][section.name].comparison.css;
          }

          if (targetContents[target][section.name] === undefined) {
            return undefined;
          }

          return {
            target,
            code: targetContents[target][section.name].code,
            altCode: targetContents[target][section.name].altCode,
          };
        })
        .filter(target => target);
    });
  }

  _exportData(): any[] {
    return this.sections.map(section => section.export());
  }

  _exportOpts() {
    return {
      title: this.opts.name,
      style: this.opts.style,
    };
  }

  async serve() {
    const data = this._exportData();
    const opts = this._exportOpts();
    try {
      await fs.ensureDir(this._filepath.lib);
      await fs.writeFile(this._filepath.data, JSON.stringify(data));
      await fs.writeFile(this._filepath.opts, JSON.stringify(opts));
    } catch (err) {
      console.log(err);
    }

    if (this.served) {
      return;
    }

    const matches = Object.values(this.opts.targetDirs).map(dir => {
      // $FlowFixMe
      return path.join(this.opts.base, dir, '**/*.+(css|sass|scss|less|styl)');
    });

    console.log(chalk.gray('watching...'));
    matches.forEach(match => {
      console.log(' - ' + match);
    });
    console.log();

    const nuxt = await this.view.build();

    this.server = new Server({
      // port: opts.port,
      files: [
        {
          match: matches,
          fn: async () => {
            await this.prepare();
            await this.serve();
          },
        },
      ],
      middleware: [
        async (req: any, res: any, next: any) => {
          await nuxt.render(req, res);
          setTimeout(() => {
            next();
          }, 500);
        },
      ],
    });

    this.served = true;
    setTimeout(() => {
      this.server.serve();
    }, 3000);
  }

  async generate(base?: string) {
    await aru('dataFile', fs.access(this._filepath.lib, fs.constants.F_OK));
    await aru.left('dataFile', async () => {
      await fs.mkdir(this._filepath.lib);
    });

    // const data = this.sections.map(s => {
    //   return s.export();
    // });
    const data = this._exportData();
    const opts = this._exportOpts();
    await fs.writeFile(this._filepath.data, JSON.stringify(data));
    await fs.writeFile(this._filepath.opts, JSON.stringify(opts));
    // await fs.writeFile(this._filepath.style, this.$css.toString());

    if (base !== undefined) {
      this.view.setBase(base);
    }

    try {
      await fs.access(this._filepath.data, fs.constants.F_OK);
      const rawData = await fs.readFile(this._filepath.data, 'utf-8');
      const data = JSON.parse(rawData);
      await this.view.generate(data);
    } catch (err) {}
  }
}
<commit_msg>Fix gap each the sections<commit_after>/* @flow */
import fs from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
import fastDiff from 'fast-diff';
import pGlob from 'glob-promise';
import pMap from 'p-map';
import matter from 'gray-matter';
import zipObject from 'lodash.zipobject';
import uniq from 'lodash.uniq';
import pkgDir from 'pkg-dir';
import toml from 'toml';
import aru from 'aru';
import {readonly} from 'core-decorators';
import compiler from './compiler';
import Section from './section';
import Server from './server';
import View from './view';
import {Compiled} from './compiler';

export interface Content {
  code: string;
  comparison: Compiled;
}
export type YosugaTarget = 'css' | 'less' | 'sass' | 'stylus' | 'postcss';
export type YosugaTargetDirs = {[target: YosugaTarget]: string};
export interface YosugaOptions {
  // origin: string;
  name: string;
  port: number;
  main: YosugaTarget;
  base: string;
  targets: YosugaTarget[];
  targetDirs: YosugaTargetDirs;
  style: {
    fontSize?: string,
    accentColor?: string,
  };
}

// ¯\_(ツ)_/¯
export type YosugaDefaultOptions = $Diff<
  YosugaOptions & {
    base: string,
    targets: any,
    style: {
      fontSize?: string,
      accentColor?: string,
    },
  },
  YosugaOptions,
>;

export interface YosugaServerOptions {
  port: number;
  watch({srcDir: string}): string;
  force: boolean;
}

export default class Yosuga {
  sections: Section[];

  /**
   * Server used for development
   */
  server: Server;

  /**
   * Mainly Nuxt
   */
  view: View;

  /**
   * Option for Yosuga
   */
  opts: YosugaOptions;

  /**
   * Theme builder using Postcss
   */
  // $css: any;

  /**
   * Whether the server is built
   */
  served: boolean;

  /**
   * Directory paths
   * @prop lib Dirname for nuxt/lib
   * @prop data Filename for nuxt/lib/data.json
   * @prop opts Filename for nuxt/lib/opts.json
   * @prop style Dirname for nuxt/static/yosuga.css
   */
  _filepath: {
    lib: string,
    data: string,
    opts: string,
    style: string,
  };

  /**
   * Yosuga's default options
   */
  @readonly
  static defaultOpts: YosugaDefaultOptions = {
    name: '',
    port: 3333,
    main: 'css',
    targetDirs: {
      yosuga: 'yosuga',
      css: 'css',
      postcss: 'postcss',
      sass: 'sass',
      less: 'less',
      stylus: 'stylus',
    },
    style: {
      fontSize: '14px',
      accentColor: '#cb1b45',
    },
  };

  constructor(opts: any = Yosuga.defaultOpts) {
    this.sections = [];
    this.view = new View(opts);
    this.opts = {...Yosuga.defaultOpts, ...opts};
    this.opts.style = {
      ...(Yosuga.defaultOpts: any).style,
      ...(opts.style || {}),
    };
    this._filepath = {
      lib: path.join(__dirname, '../lib/view/lib/'),
      data: path.join(__dirname, '../lib/view/lib/data.json'),
      opts: path.join(__dirname, '../lib/view/lib/opts.json'),
      style: path.join(__dirname, '../lib/view/static/yosuga.css'),
    };
  }

  /**
   * Determine whether the target tool exists directory
   */
  async _getTargets(): Promise<YosugaTarget[]> {
    const result: YosugaTarget[] = [];
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.css),
      )
    ) {
      result.push('css');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.postcss),
      )
    ) {
      result.push('postcss');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.sass),
      )
    ) {
      result.push('sass');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.less),
      )
    ) {
      result.push('less');
    }
    if (
      await fs.pathExists(
        path.resolve(this.opts.base, this.opts.targetDirs.stylus),
      )
    ) {
      result.push('stylus');
    }

    return result;
  }

  async _getTargetContents(
    compiler,
    targets: YosugaTarget[],
  ): {[target: YosugaTarget]: {[filename: string]: Content}} {
    const items = await pMap(targets, async target => {
      const targetDirname = path.resolve(this.opts.base, target);

      const pattern = path.join(targetDirname, '**/*.*');
      const comparisonPattern = path.join(targetDirname, '**/*.yosuga.*');
      const filenames = (await pGlob(pattern)).filter(
        filename => !filename.includes('.yosuga.'),
      );
      const comparisonFilenames = await pGlob(comparisonPattern);
      const basenames = filenames.map(filename => {
        const extname = path.extname(filename);
        return path.basename(filename, extname);
      });

      const contents = await pMap(filenames, async filename => {
        const code = await fs.readFile(filename, 'utf-8');
        let css = code;
        if (path.extname(filename) === '.css') {
          css = await compiler.standardize(code);
        }
        return {
          code,
          comparison: {css},
        };
      });
      const map = zipObject(basenames, contents);

      await pMap(comparisonFilenames, async filename => {
        const extname = path.extname(filename);
        const key = path.basename(filename, `.yosuga${extname}`);
        map[key].altCode = await fs.readFile(filename, 'utf-8');
        map[key].comparison = await compiler[extname](filename, 'output.css');

        // eslint-disable-next-line no-warning-comments
        // TODO: sourcemap merge
        map[key].comparison.css = await compiler.standardize(
          map[key].comparison.css,
        );
      });

      return map;
    });

    const readyState = zipObject(targets, items);
    // section names
    // e.g.) [ 'button', 'card', 'nav', 'table' ]
    const targetSectionKeys = Object.keys(readyState[this.opts.main]);

    const result = targetSectionKeys.every(key => {
      const items: any = this._getComparisonBy(readyState, key);
      const valid = this._diff(items, key);
      if (!valid) {
        for (const content of (Object.values(readyState): any)) {
          delete content[key];
        }
      }
      return valid;
    });

    if (!result) {
      throw new Error(
        chalk.red('ERR! There are sections where each is not equal'),
      );
    }

    return readyState;
  }

  _getComparisonBy(
    map: {[target: YosugaTarget]: {[filename: string]: Content}},
    key: string,
  ): Compiled[] {
    const groups: any = Object.entries(map);
    return groups
      .map(([target, items]: [YosugaTarget, {[filename: string]: Content}]) => {
        const comparison = (items[key] || {}).comparison;
        if (comparison === undefined) {
          return undefined;
        }

        (comparison: any).target = target;
        return comparison;
      })
      .filter(comparison => comparison);
  }

  _diff(
    comparisonItems: (Compiled & {target: YosugaTarget})[],
    key: string,
  ): boolean {
    const baseItem = comparisonItems[0];

    return comparisonItems.slice(1).every(item => {
      const diffed = fastDiff(baseItem.css, item.css);

      if (diffed.length === 1) {
        return true;
      }

      const result = diffed
        .map(chunks => {
          switch (chunks[0]) {
            case -1: {
              return chalk.green(chunks[1]);
            }
            case 0:
            default: {
              return chunks[1];
            }
            case 1: {
              return chalk.red(chunks[1]);
            }
          }
        })
        .join('');

      if (process.env.NODE_ENV !== 'test') {
        console.log(chalk.bold(`${chalk.yellow('`' + key + '`')} is missing`));
        console.log(
          chalk.bold(
            `${chalk.gray(' - ')}${chalk.green(baseItem.target)}${chalk.gray(
              ' / ',
            )}${chalk.red(item.target)}`,
          ),
        );
        console.log();
        console.log(result);
        console.log();
        console.log();
        console.log();
      }
      return false;
    });
  }

  async _getSections(basenames: string[]): Promise<Section[]> {
    // eslint-disable-next-line no-return-await
    return await pMap(basenames, async name => {
      const filePattern = path.join(
        this.opts.base,
        (this.opts.targetDirs: any).yosuga,
        `*${name}.html`,
      );
      const [filename] = await pGlob(filePattern);
      if (!filename) {
        throw new Error(`ERR! ${name}.html was not found`);
      }
      const content = await fs.readFile(filename, 'utf-8');
      const result = matter(content, {
        language: 'toml',
        engines: {
          toml: {
            parse: toml.parse.bind(toml),
          },
        },
      });
      const parsed = path.parse(filename);
      parsed.base = parsed.base.replace(/^\d+[.:|]/, '');
      result.filename = path.format((parsed: any));

      return new Section(name, result);
    });
  }

  async prepare(): Promise<void> {
    if (this.opts.name === '') {
      const pkgDirname = await pkgDir(process.cwd());
      try {
        const pkg = await fs.readJson(path.join(pkgDirname, 'package.json'));
        this.opts.name = pkg.name;
      } catch (_) {
        // default
        this.opts.name = 'Yosuga';
      }
    }

    if (this.opts.base === undefined) {
      this.opts.base = await pkgDir(process.cwd());
    }

    let targets = await this._getTargets();
    targets = uniq([this.opts.main, ...targets]);
    const targetContents = await this._getTargetContents(compiler, targets);
    const validKeys = Object.keys(targetContents[this.opts.main]);

    if (validKeys.length === 0) {
      throw new Error('ERR! There is no valid item');
    }

    this.sections = await (this: any)._getSections(validKeys);

    this.sections.forEach(section => {
      section.items = targets
        .map(target => {
          if (target === this.opts.main) {
            section.css = targetContents[target][section.name].comparison.css;
          }

          if (targetContents[target][section.name] === undefined) {
            return undefined;
          }

          return {
            target,
            code: targetContents[target][section.name].code,
            altCode: targetContents[target][section.name].altCode,
          };
        })
        .filter(target => target);
    });
  }

  _exportData(): any[] {
    return this.sections.map(section => section.export());
  }

  _exportOpts() {
    return {
      title: this.opts.name,
      style: this.opts.style,
    };
  }

  async serve() {
    const data = this._exportData();
    const opts = this._exportOpts();
    try {
      await fs.ensureDir(this._filepath.lib);
      await fs.writeFile(this._filepath.data, JSON.stringify(data));
      await fs.writeFile(this._filepath.opts, JSON.stringify(opts));
    } catch (err) {
      console.log(err);
    }

    if (this.served) {
      return;
    }

    const matches = Object.values(this.opts.targetDirs).map(dir => {
      // $FlowFixMe
      return path.join(this.opts.base, dir, '**/*.+(css|sass|scss|less|styl)');
    });

    console.log(chalk.gray('watching...'));
    matches.forEach(match => {
      console.log(' - ' + match);
    });
    console.log();

    const nuxt = await this.view.build();

    this.server = new Server({
      // port: opts.port,
      files: [
        {
          match: matches,
          fn: async () => {
            await this.prepare();
            await this.serve();
          },
        },
      ],
      middleware: [
        async (req: any, res: any, next: any) => {
          await nuxt.render(req, res);
          setTimeout(() => {
            next();
          }, 500);
        },
      ],
    });

    this.served = true;
    setTimeout(() => {
      this.server.serve();
    }, 3000);
  }

  async generate(base?: string) {
    await aru('dataFile', fs.access(this._filepath.lib, fs.constants.F_OK));
    await aru.left('dataFile', async () => {
      await fs.mkdir(this._filepath.lib);
    });

    // const data = this.sections.map(s => {
    //   return s.export();
    // });
    const data = this._exportData();
    const opts = this._exportOpts();
    await fs.writeFile(this._filepath.data, JSON.stringify(data));
    await fs.writeFile(this._filepath.opts, JSON.stringify(opts));
    // await fs.writeFile(this._filepath.style, this.$css.toString());

    if (base !== undefined) {
      this.view.setBase(base);
    }

    try {
      await fs.access(this._filepath.data, fs.constants.F_OK);
      const rawData = await fs.readFile(this._filepath.data, 'utf-8');
      const data = JSON.parse(rawData);
      await this.view.generate(data);
    } catch (err) {}
  }
}
<|endoftext|>


<commit_before>module.exports = {
	Parser: require("./Parser.js"),
	DefaultHandler: require("./DefaultHandler.js"),
	FeedHandler: require("./FeedHandler.js"),
	ElementType: require("./ElementType.js"),
	DomUtils: require("./DomUtils.js")
}<commit_msg>Dynamically load files when requested in index.js<commit_after>module.exports = {
	get Parser(){
		Object.defineProperty(this, "Parser", {value:require("./Parser.js")});
		return this.Parser;
	},
	get DefaultHandler(){
		Object.defineProperty(this, "DefaultHandler", {value:require("./DefaultHandler.js")});
		return this.DefaultHandler;
	},
	get FeedHandler(){
		Object.defineProperty(this, "FeedHandler", {value:require("./FeedHandler.js")});
		return this.FeedHandler;
	},
	get ElementType(){
		Object.defineProperty(this, "ElementType", {value:require("./ElementType.js")});
		return ElementType;
	},
	get DomUtils(){
		Object.defineProperty(this, "DomUtils", {value:require("./DomUtils.js")});
		return this.DomUtils;
	}
}<|endoftext|>


<commit_before>const Assert = require("assert");
const Hoek = require("@hapi/hoek");
const Joi = require("@hapi/joi");

const internals = {
  mongoose: null,
  joiGlobalOptions: {}
};

internals.root = (mongoose, options, subdocumentOptions) => {
  Assert(mongoose);
  internals.mongoose = mongoose;
  internals.joiGlobalOptions = options || null;
  internals.subdocumentOptions = subdocumentOptions || null;

  return {
    convert: internals.convert,
    mongooseValidateWrapper: internals.mongooseValidateWrapper
  };
};

internals.convert = joiObject => {
  if (joiObject === undefined) {
    throw new Error("Ensure the value you're trying to convert exists!");
  }

  if (!Joi.isSchema(joiObject)) {
    joiObject = Joi.object(joiObject);
  }
  const output = {};

  if (joiObject._flags.default !== undefined) {
    output.default = joiObject._flags.default;
  }

  if (joiObject.$_terms.metas.length > 0) {
    const toClass = {}.toString;
    // console.log(JSON.stringify(joiObject, null, '  '));

    // Iterate through the array
    for (const meta of joiObject.$_terms.metas) {
      // Only add objects
      if (toClass.call(meta) !== "[object Object]") {
        continue;
      }

      if (meta._mongoose) {
        for (const key of Object.keys(meta._mongoose)) {
          // Check for _objectId
          if (
            key === "type" &&
            meta._mongoose[key] === "ObjectId"
          ) {
            output.type = internals.mongoose.Schema.Types.ObjectId;

            const originalJoiObject = Hoek.clone(joiObject);

            // joiObject = Joi.alternatives([
            //     joiObject,
            //     Joi.object().type(internals.mongoose.Schema.Types.ObjectId)
            // ]);

            joiObject._isObjectId = true;
            originalJoiObject._isObjectId = true;
            joiObject.$_terms.metas = originalJoiObject.$_terms.metas;
            joiObject._flags = originalJoiObject._flags;

            output.validate = internals.mongooseValidateWrapper.bind(
              this,
              originalJoiObject
            );
          } else {
            output[key] = meta._mongoose[key];
          }
        }
      }
    }
  }

  if (joiObject.type === "object") {
    //  Allow for empty object - https://github.com/hapijs/joi/blob/v9.0.0-3/API.md#object
    if (!joiObject.$_terms.keys)
      return { type: internals.mongoose.Schema.Types.Mixed };

    joiObject.$_terms.keys.forEach((child) => {
      output[child.key] = internals.convert(child.schema);

      if (internals.isObjectId(child.schema)) {
        child.schema._isObjectId = true;
        // child.schema = Joi.alternatives([
        //     Hoek.clone(child.schema),
        //     Joi.object().type(internals.mongoose.Types.ObjectId)
        // ]);
      }
    });

    return output;
  }

  output.validate = {
    validator: internals.mongooseValidateWrapper.bind(this, joiObject)
  };

  // We don't want the required key added onto objects, hence why its here.
  // If it's added onto objects, Mongoose complains because it can't
  // understand the type 'true'. #truestory #lol
  if (joiObject._flags.presence === "required") {
    output.required = true;
  }

  if (output.type) {
    return output;
  }

  output.type = internals.typeDeterminer(joiObject);

  // If this is an array, let's get rid of the validation cos it causes major
  // beef with validation
  if (Array.isArray(output.type)) {
    delete output.validate;
  }

  return output;
};

internals.typeDeterminer = joiObject => {
  if (joiObject.type === "string") {
    // If the type has already been set, that's probably
    // because it was an ObjectId. In this case, don't set the
    // type as a string.
    return String;
  }

  if (joiObject.type === "number") {
    return Number;
  }

  if (joiObject.type === "date") {
    return Date;
  }

  if (joiObject.type === "boolean") {
    return Boolean;
  }

  let types = {};
  const type = [];
  let i = 0;
  let firstKey;

  if (joiObject.type === "array") {
    // Go through each of the children in the array and get their types
    for (const meta of joiObject.$_terms.items) {
      if (types[meta.type]) {
        types[meta.type]++;
      } else {
        types[meta.type] = 1;
      }
    }

    // If there are multiple types, there's not much else we can do as far as Mongoose goes...
    if (Object.keys(types).length > 1) {
      type.push(internals.mongoose.Schema.Types.Mixed);
      return type;
    }

    // If there are multiple of the same type, this means that there are different schemas.
    // This is alright cos we know they're all the same type
    firstKey = Object.keys(types)[0];
    if (types[firstKey] > 1) {
      type.push(internals.typeDeterminer({ type: firstKey }));
      return type;
    }

    if (joiObject.$_terms.items.length === 0) {
      return type;
    }

    // Collate all meta objects because these need to be pushed into the schema options
    let schemaOptions = joiObject.$_terms.metas.reduce(
      (options, currentOption) => Object.assign({}, options._mongoose, currentOption._mongoose),
      {}
    );

    // Combine the explicit schema options with the global subdocument options
    if (internals.subdocumentOptions) {
      schemaOptions = Object.assign(
        {},
        internals.subdocumentOptions,
        schemaOptions
      );
    }

    if (
      internals.typeDeterminer(joiObject.$_terms.items[0]) !== Object
    ) {
      type.push(internals.convert(joiObject.$_terms.items[0]));
    } else {
      type.push(
        new internals.mongoose.Schema(
          internals.convert(joiObject.$_terms.items[0]),
          schemaOptions
        )
      );
    }

    return type;
  }

  if (joiObject.type === "alternatives") {
    types = {};

    if (joiObject.$_terms.matches.length === 0) {
      return internals.mongoose.Schema.Types.Mixed;
    }

    // Go through each of the children in the array and get their types
    for (const match of joiObject.$_terms.matches) {
      types[match.schema.type] = types[
        match.schema.type
      ]
        ? types[match.schema.type] + 1
        : (types[match.schema.type] = 1);
    }

    // If there are multiple types, there's not much else we can do as far as Mongoose goes...
    if (Object.keys(types).length > 1) {
      return internals.mongoose.Schema.Types.Mixed;
    }

    // If there are multiple of the same type, this means that there are different schemas, but the same type :D
    firstKey = Object.keys(types)[0];
    if (types[firstKey] > 1) {
      return internals.typeDeterminer({ type: firstKey });
    }

    // If we're here, it's because there's a single type, and one schema. So actually, an alternative didn't need to be used...
    return internals.typeDeterminer(joiObject.$_terms.matches[0].schema);

    // // If there are multiple types in the items, set the type to mixed
    // mixedTypeFound = false;

    // if (joiObject.$_terms.matches.length === 0) {
    //     mixedTypeFound = true;
    // }

    // types = {};
    // joiObject.$_terms.matches.forEach(function (item) {

    //     types[item.schema.type] = true;
    // });

    // // If we have one type, then all array items are this
    // if (Object.keys(types).length === 1) {
    //     output = internals.convert(joiObject.$_terms.matches[0].schema);
    // } else {
    //     mixedTypeFound = true;
    // }

    // if (mixedTypeFound) {
    //     return internals.mongoose.Schema.Types.Mixed;
    // }

    // return output;
  }

  if (joiObject.type === "object") {
    return Object;
  }

  if (joiObject.type === "any") {
    return internals.mongoose.Schema.Types.Mixed;
  }

  throw new TypeError(
    'Unsupported Joi type: "' +
      joiObject.type +
      "\"! Raise an issue on GitHub if you'd like it to be added!"
  );
};

internals.isObjectId = joiObject => {
  if (joiObject.$_terms.metas.length > 0) {
    const toClass = {}.toString;

    for (const meta of joiObject.$_terms.metas) {
      // Only add objects
      if (toClass.call(meta) !== "[object Object]") {
        continue;
      }

      if (meta._mongoose) {
        for (const key in meta._mongoose) {
          // Check for _objectId
          if (key === "type" && meta._mongoose[key] === "ObjectId") {
            return true;
          } else {
            return false;
          }
        }
      }
    }
  }

  return false;
};

internals.mongooseValidateWrapper = async (originalJoiSchema, value) => {
  let joiSchema = Hoek.clone(originalJoiSchema);

  if (joiSchema._isObjectId) {
    joiSchema = Joi.alternatives(
      joiSchema,
      Joi.object().instance(internals.mongoose.Types.ObjectId)
    );
  }

  try {
    const { error } = joiSchema.validate(value, internals.joiGlobalOptions);
    if (error) return false;
    return true;
  } catch (err) {
    try {
      await joiSchema.validateAsync(value, internals.joiGlobalOptions);
      return true;
    } catch (err) {
      return false;
    }
  }
};

module.exports = internals.root;
<commit_msg>refactor: get the values using Object.entries() instead of Object.keys() as index<commit_after>const Assert = require("assert");
const Hoek = require("@hapi/hoek");
const Joi = require("@hapi/joi");

const internals = {
  mongoose: null,
  joiGlobalOptions: {}
};

internals.root = (mongoose, options, subdocumentOptions) => {
  Assert(mongoose);
  internals.mongoose = mongoose;
  internals.joiGlobalOptions = options || null;
  internals.subdocumentOptions = subdocumentOptions || null;

  return {
    convert: internals.convert,
    mongooseValidateWrapper: internals.mongooseValidateWrapper
  };
};

internals.convert = joiObject => {
  if (joiObject === undefined) {
    throw new Error("Ensure the value you're trying to convert exists!");
  }

  if (!Joi.isSchema(joiObject)) {
    joiObject = Joi.object(joiObject);
  }
  const output = {};

  if (joiObject._flags.default !== undefined) {
    output.default = joiObject._flags.default;
  }

  if (joiObject.$_terms.metas.length > 0) {
    const toClass = {}.toString;
    // console.log(JSON.stringify(joiObject, null, '  '));

    // Iterate through the array
    for (const meta of joiObject.$_terms.metas) {
      // Only add objects
      if (toClass.call(meta) !== "[object Object]") {
        continue;
      }

      if (meta._mongoose) {
        for (const [key, value] of Object.entries(meta._mongoose)) {
          // Check for _objectId
          if (
            key === "type" &&
            value === "ObjectId"
          ) {
            output.type = internals.mongoose.Schema.Types.ObjectId;

            const originalJoiObject = Hoek.clone(joiObject);

            // joiObject = Joi.alternatives([
            //     joiObject,
            //     Joi.object().type(internals.mongoose.Schema.Types.ObjectId)
            // ]);

            joiObject._isObjectId = true;
            originalJoiObject._isObjectId = true;
            joiObject.$_terms.metas = originalJoiObject.$_terms.metas;
            joiObject._flags = originalJoiObject._flags;

            output.validate = internals.mongooseValidateWrapper.bind(
              this,
              originalJoiObject
            );
          } else {
            output[key] = value;
          }
        }
      }
    }
  }

  if (joiObject.type === "object") {
    //  Allow for empty object - https://github.com/hapijs/joi/blob/v9.0.0-3/API.md#object
    if (!joiObject.$_terms.keys)
      return { type: internals.mongoose.Schema.Types.Mixed };

    joiObject.$_terms.keys.forEach((child) => {
      output[child.key] = internals.convert(child.schema);

      if (internals.isObjectId(child.schema)) {
        child.schema._isObjectId = true;
        // child.schema = Joi.alternatives([
        //     Hoek.clone(child.schema),
        //     Joi.object().type(internals.mongoose.Types.ObjectId)
        // ]);
      }
    });

    return output;
  }

  output.validate = {
    validator: internals.mongooseValidateWrapper.bind(this, joiObject)
  };

  // We don't want the required key added onto objects, hence why its here.
  // If it's added onto objects, Mongoose complains because it can't
  // understand the type 'true'. #truestory #lol
  if (joiObject._flags.presence === "required") {
    output.required = true;
  }

  if (output.type) {
    return output;
  }

  output.type = internals.typeDeterminer(joiObject);

  // If this is an array, let's get rid of the validation cos it causes major
  // beef with validation
  if (Array.isArray(output.type)) {
    delete output.validate;
  }

  return output;
};

internals.typeDeterminer = joiObject => {
  if (joiObject.type === "string") {
    // If the type has already been set, that's probably
    // because it was an ObjectId. In this case, don't set the
    // type as a string.
    return String;
  }

  if (joiObject.type === "number") {
    return Number;
  }

  if (joiObject.type === "date") {
    return Date;
  }

  if (joiObject.type === "boolean") {
    return Boolean;
  }

  let types = {};
  const type = [];
  let i = 0;
  let firstKey;

  if (joiObject.type === "array") {
    // Go through each of the children in the array and get their types
    for (const meta of joiObject.$_terms.items) {
      if (types[meta.type]) {
        types[meta.type]++;
      } else {
        types[meta.type] = 1;
      }
    }

    // If there are multiple types, there's not much else we can do as far as Mongoose goes...
    if (Object.keys(types).length > 1) {
      type.push(internals.mongoose.Schema.Types.Mixed);
      return type;
    }

    // If there are multiple of the same type, this means that there are different schemas.
    // This is alright cos we know they're all the same type
    firstKey = Object.keys(types)[0];
    if (types[firstKey] > 1) {
      type.push(internals.typeDeterminer({ type: firstKey }));
      return type;
    }

    if (joiObject.$_terms.items.length === 0) {
      return type;
    }

    // Collate all meta objects because these need to be pushed into the schema options
    let schemaOptions = joiObject.$_terms.metas.reduce(
      (options, currentOption) => Object.assign({}, options._mongoose, currentOption._mongoose),
      {}
    );

    // Combine the explicit schema options with the global subdocument options
    if (internals.subdocumentOptions) {
      schemaOptions = Object.assign(
        {},
        internals.subdocumentOptions,
        schemaOptions
      );
    }

    if (
      internals.typeDeterminer(joiObject.$_terms.items[0]) !== Object
    ) {
      type.push(internals.convert(joiObject.$_terms.items[0]));
    } else {
      type.push(
        new internals.mongoose.Schema(
          internals.convert(joiObject.$_terms.items[0]),
          schemaOptions
        )
      );
    }

    return type;
  }

  if (joiObject.type === "alternatives") {
    types = {};

    if (joiObject.$_terms.matches.length === 0) {
      return internals.mongoose.Schema.Types.Mixed;
    }

    // Go through each of the children in the array and get their types
    for (const match of joiObject.$_terms.matches) {
      types[match.schema.type] = types[
        match.schema.type
      ]
        ? types[match.schema.type] + 1
        : (types[match.schema.type] = 1);
    }

    // If there are multiple types, there's not much else we can do as far as Mongoose goes...
    if (Object.keys(types).length > 1) {
      return internals.mongoose.Schema.Types.Mixed;
    }

    // If there are multiple of the same type, this means that there are different schemas, but the same type :D
    firstKey = Object.keys(types)[0];
    if (types[firstKey] > 1) {
      return internals.typeDeterminer({ type: firstKey });
    }

    // If we're here, it's because there's a single type, and one schema. So actually, an alternative didn't need to be used...
    return internals.typeDeterminer(joiObject.$_terms.matches[0].schema);

    // // If there are multiple types in the items, set the type to mixed
    // mixedTypeFound = false;

    // if (joiObject.$_terms.matches.length === 0) {
    //     mixedTypeFound = true;
    // }

    // types = {};
    // joiObject.$_terms.matches.forEach(function (item) {

    //     types[item.schema.type] = true;
    // });

    // // If we have one type, then all array items are this
    // if (Object.keys(types).length === 1) {
    //     output = internals.convert(joiObject.$_terms.matches[0].schema);
    // } else {
    //     mixedTypeFound = true;
    // }

    // if (mixedTypeFound) {
    //     return internals.mongoose.Schema.Types.Mixed;
    // }

    // return output;
  }

  if (joiObject.type === "object") {
    return Object;
  }

  if (joiObject.type === "any") {
    return internals.mongoose.Schema.Types.Mixed;
  }

  throw new TypeError(
    'Unsupported Joi type: "' +
      joiObject.type +
      "\"! Raise an issue on GitHub if you'd like it to be added!"
  );
};

internals.isObjectId = joiObject => {
  if (joiObject.$_terms.metas.length > 0) {
    const toClass = {}.toString;

    for (const meta of joiObject.$_terms.metas) {
      // Only add objects
      if (toClass.call(meta) !== "[object Object]") {
        continue;
      }

      if (meta._mongoose) {
        for (const key in meta._mongoose) {
          // Check for _objectId
          if (key === "type" && meta._mongoose[key] === "ObjectId") {
            return true;
          } else {
            return false;
          }
        }
      }
    }
  }

  return false;
};

internals.mongooseValidateWrapper = async (originalJoiSchema, value) => {
  let joiSchema = Hoek.clone(originalJoiSchema);

  if (joiSchema._isObjectId) {
    joiSchema = Joi.alternatives(
      joiSchema,
      Joi.object().instance(internals.mongoose.Types.ObjectId)
    );
  }

  try {
    const { error } = joiSchema.validate(value, internals.joiGlobalOptions);
    if (error) return false;
    return true;
  } catch (err) {
    try {
      await joiSchema.validateAsync(value, internals.joiGlobalOptions);
      return true;
    } catch (err) {
      return false;
    }
  }
};

module.exports = internals.root;
<|endoftext|>


<commit_before>const events =  require('events');
const PubSub = require('@google-cloud/pubsub');
const path = require('path');
const uuid = require('uuid');

const namePrefix = 'parse-server';

let emitter = new events.EventEmitter();

// if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
//   throw 'set GOOGLE_APPLICATION_CREDENTIALS variable (see https://googlecloudplatform.github.io/google-cloud-node/#/docs/pubsub/0.8.0/guides/authentication)';
// }

// if (!process.env.GCLOUD_PROJECT) {
//   throw 'set GCLOUD_PROJECT variable (see https://googlecloudplatform.github.io/google-cloud-node/#/docs/pubsub/0.8.0/guides/authentication)';
// }

const pubsubClient = PubSub();

class Publisher {

  constructor(emitter) {
    this.emitter = emitter;
  }

  publish(channel, message) {

    let topicName = `${namePrefix}-${channel}`;
    const topic = pubsubClient.topic(topicName);

    topic.publish(message, (err, messageIds) => {
      if (err) {
        return;
      }
    });
  }
};

class Subscriber extends events.EventEmitter {

  constructor(options) {
    super();
    this.disableFanOut = options.disableFanOut || false;
  }

  subscribe(channel) {

    let topicName = `${namePrefix}-${channel}`;
    const topic = pubsubClient.topic(topicName);

    /**
     * check if the topic that we want to create exist
     * if the topic not exist create a new topic based on the channel name
     * the topic name will be parse-server-{channel_name}
     *  */
    topic.exists((err, exists) => {
      if (!exists && !err) {
        topic.create((err, topic, apiResponse) => {
          if (err) {
            return;
          }
          this._createSubscription(topic,channel);
        });
      } else {
        this._createSubscription(topic,channel);
      }
    });
  }

  unsubscribe(channel) {
    // Here we should get all the subscriptions under the channel topic and unsubscribe them
  }

  _createSubscription(topic, channel) {
    let subscriptionName;
    if (!this.disableFanOut) {
      const subscriptionUUID = uuid.v1();
      subscriptionName = `${namePrefix}-${channel}-${subscriptionUUID}`;
    } else {
      subscriptionName = `${namePrefix}-${channel}`;
    }

    topic.subscribe(subscriptionName, (err, subscription) => {

      if (err) {
        console.error(`Failed to create subscription ${err}`);
        return;
      }

      console.log(`Subscription ${subscription.name} created.`);

      function deleteSubscription() {
        removeListeners();
        console.log('Subscriber: Signal received, deleting subscription');
        subscription.delete().then(() => {
          console.log('Subscriber: subscription deleted...');
        }, (err) => {
          console.error(`Subscriber: Error deleting subscription`, err);
        });
      }

      function messageHandler(message) {
          if (message.ackId) {
            message.ack();
          }
          let topicName = `${namePrefix}-${channel}`;
          this.emit('message', channel , message.data);
      };

      function errorHandler(err) {
        console.error('Subscriber Error: ', err);
        // Handle when subscription gets deleted
        // Attempt to recreate it
        if (err.code == 404) {
          console.log('Subscriber: will recreate subscription');
          removeListeners();
          this._createSubscription(topic, channel);
        }
      }

      const onMessage = messageHandler.bind(this);
      const onError = errorHandler.bind(this);

      // Remove the listenerds
      function removeListeners() {
        subscription.removeListener('message', onMessage);
        subscription.removeListener('error', onError);
        process.removeListener('SIGTERM', deleteSubscription);
        process.removeListener('SIGINT', deleteSubscription);
      }

      // Handle termination, delete the subscription (require graceful shutdowm)
      process.on('SIGTERM', deleteSubscription);
      process.on('SIGINT', deleteSubscription);

      // Bind the subscription
      subscription.on('message', onMessage);
      subscription.on('error', onError);
    });
  }
}

function createPublisher() {
  return new Publisher(emitter);
}

function createSubscriber(options) {
  return new Subscriber(options);
}

module.exports = {
  createPublisher,
  createSubscriber
};
<commit_msg>Don't delete subscription if fanout is disabled<commit_after>const events =  require('events');
const PubSub = require('@google-cloud/pubsub');
const path = require('path');
const uuid = require('uuid');

const namePrefix = 'parse-server';

let emitter = new events.EventEmitter();

// if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
//   throw 'set GOOGLE_APPLICATION_CREDENTIALS variable (see https://googlecloudplatform.github.io/google-cloud-node/#/docs/pubsub/0.8.0/guides/authentication)';
// }

// if (!process.env.GCLOUD_PROJECT) {
//   throw 'set GCLOUD_PROJECT variable (see https://googlecloudplatform.github.io/google-cloud-node/#/docs/pubsub/0.8.0/guides/authentication)';
// }

const pubsubClient = PubSub();

class Publisher {

  constructor(emitter) {
    this.emitter = emitter;
  }

  publish(channel, message) {

    let topicName = `${namePrefix}-${channel}`;
    const topic = pubsubClient.topic(topicName);

    topic.publish(message, (err, messageIds) => {
      if (err) {
        return;
      }
    });
  }
};

class Subscriber extends events.EventEmitter {

  constructor(options) {
    super();
    this.disableFanOut = options.disableFanOut || false;
  }

  subscribe(channel) {

    let topicName = `${namePrefix}-${channel}`;
    const topic = pubsubClient.topic(topicName);

    /**
     * check if the topic that we want to create exist
     * if the topic not exist create a new topic based on the channel name
     * the topic name will be parse-server-{channel_name}
     *  */
    topic.exists((err, exists) => {
      if (!exists && !err) {
        topic.create((err, topic, apiResponse) => {
          if (err) {
            return;
          }
          this._createSubscription(topic,channel);
        });
      } else {
        this._createSubscription(topic,channel);
      }
    });
  }

  unsubscribe(channel) {
    // Here we should get all the subscriptions under the channel topic and unsubscribe them
  }

  _createSubscription(topic, channel) {
    let subscriptionName;
    if (!this.disableFanOut) {
      const subscriptionUUID = uuid.v1();
      subscriptionName = `${namePrefix}-${channel}-${subscriptionUUID}`;
    } else {
      subscriptionName = `${namePrefix}-${channel}`;
    }

    topic.subscribe(subscriptionName, (err, subscription) => {

      if (err) {
        console.error(`Failed to create subscription ${err}`);
        return;
      }

      console.log(`Subscription ${subscription.name} created.`);

      function deleteSubscription() {
        removeListeners();
        console.log('Subscriber: Signal received, deleting subscription');
        subscription.delete().then(() => {
          console.log('Subscriber: subscription deleted...');
        }, (err) => {
          console.error(`Subscriber: Error deleting subscription`, err);
        });
      }

      function messageHandler(message) {
          if (message.ackId) {
            message.ack();
          }
          let topicName = `${namePrefix}-${channel}`;
          this.emit('message', channel , message.data);
      };

      function errorHandler(err) {
        console.error('Subscriber Error: ', err);
        // Handle when subscription gets deleted
        // Attempt to recreate it
        if (err.code == 404) {
          console.log('Subscriber: will recreate subscription');
          removeListeners();
          this._createSubscription(topic, channel);
        }
      }

      const onMessage = messageHandler.bind(this);
      const onError = errorHandler.bind(this);

      // Remove the listenerds
      function removeListeners() {
        subscription.removeListener('message', onMessage);
        subscription.removeListener('error', onError);

        if (!this.disableFanOut) {
          process.removeListener('SIGTERM', deleteSubscription);
          process.removeListener('SIGINT', deleteSubscription);
        }
      }

      // Handle termination, delete the subscription (require graceful shutdowm)
      if (!this.disableFanOut) {
        process.on('SIGTERM', deleteSubscription);
        process.on('SIGINT', deleteSubscription);
      }

      // Bind the subscription
      subscription.on('message', onMessage);
      subscription.on('error', onError);
    });
  }
}

function createPublisher() {
  return new Publisher(emitter);
}

function createSubscriber(options) {
  return new Subscriber(options);
}

module.exports = {
  createPublisher,
  createSubscriber
};
<|endoftext|>


<commit_before>/* jshint esversion:6, node: true */

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const app = express();

app.set('port', (process.env.PORT || 5000));

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));

// parse application/json
app.use(bodyParser.json());

// index
app.get('/', function (req, res) {
	res.send('hello world i am a secret bot');
});

// for facebook verification
app.get('/webhook/', function (req, res) {
	if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
		res.send(req.query['hub.challenge']);
	} else {
		res.send('Error, wrong token');
	}
});

// to post data
app.post('/webhook/', function (req, res) {
	let messaging_events = req.body.entry[0].messaging;
	for (let i = 0; i < messaging_events.length; i++) {
		let event = req.body.entry[0].messaging[i];
		let sender = event.sender.id;

		if (event.message && event.message.text) {
			let text = event.message.text;
			if (text === 'Generic') {
				sendGenericMessage(sender);
				continue;
			}

			else if (text == 'cookies'){
				var headers = {
				    'Authorization': 'bearer fd887ce5-7b15-4c1e-93a0-dc91ce883ec6'
				};

				var options = {
				    url: 'https://www.loblaws.ca/ecommerce/v2/loblaw/products/search?q=apple&page=0&pageSize=10&sort=recommended&filters=category:LSL001006000000',
				    headers: headers
				};

				function callback(error, response, body) {
				    if (!error && response.statusCode == 200) {
				        //console.log(body);
				        var resp = JSON.parse(body);
				        var results = resp["searchResults"];
				        console.log(results[0]);
				    }
				}

				request(options, callback);

			}
			sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200));
		}
		if (event.postback) {
			let text = JSON.stringify(event.postback);
			sendTextMessage(sender, "Postback received: "+text.substring(0, 200), token);
			continue;
		}
	}
	res.sendStatus(200);
});

// recommended to inject access tokens as environmental variables, e.g.
const token = process.env.PAGE_ACCESS_TOKEN;
//const token = "<PAGE_ACCESS_TOKEN>"

function sendTextMessage(sender, text) {
	let messageData = { text:text };

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

function sendGenericMessage(sender) {
	let messageData = {
		"attachment": {
			"type": "template",
			"payload": {
				"template_type": "generic",
				"elements": [{
					"title": "First card",
					"subtitle": "Element #1 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/rift.png",
					"buttons": [{
						"type": "web_url",
						"url": "https://www.messenger.com",
						"title": "web url"
					}, {
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for first element in a generic bubble",
					}],
				}, {
					"title": "Second card",
					"subtitle": "Element #2 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/gearvr.png",
					"buttons": [{
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for second element in a generic bubble",
					}],
				}]
			}
		}
	};
	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

// spin spin sugar
app.listen(app.get('port'), function() {
	console.log('running on port', app.get('port'));
});
<commit_msg>heroku<commit_after>/* jshint esversion:6, node: true */

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const request = require('request');
const app = express();

app.set('port', (process.env.PORT || 5000));

// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));

// parse application/json
app.use(bodyParser.json());

// index
app.get('/', function (req, res) {
	res.send('hello world i am a secret bot');
});

// for facebook verification
app.get('/webhook/', function (req, res) {
	if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') {
		res.send(req.query['hub.challenge']);
	} else {
		res.send('Error, wrong token');
	}
});

// to post data
app.post('/webhook/', function (req, res) {
	let messaging_events = req.body.entry[0].messaging;
	for (let i = 0; i < messaging_events.length; i++) {
		let event = req.body.entry[0].messaging[i];
		let sender = event.sender.id;

		if (event.message && event.message.text) {
			let text = event.message.text;
			if (text === 'Generic') {
				sendGenericMessage(sender);
				continue;
			}

			else if (text == 'cookies'){
				var headers = {
				    'Authorization': 'bearer fd887ce5-7b15-4c1e-93a0-dc91ce883ec6'
				};

				var options = {
				    url: 'https://www.loblaws.ca/ecommerce/v2/loblaw/products/search?q=apple&page=0&pageSize=10&sort=recommended&filters=category:LSL001006000000',
				    headers: headers
				};

				function callback(error, response, body) {
				    if (!error && response.statusCode == 200) {
				        //console.log(body);
				        var resp = JSON.parse(body);
				        var results = resp["searchResults"];
				        console.log(results[0]);
				        sendTextMessage(sender, results[0], token);
				    }
				}

				request(options, callback);

			}
			sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200));
		}
		if (event.postback) {
			let text = JSON.stringify(event.postback);
			sendTextMessage(sender, "Postback received: "+text.substring(0, 200), token);
			continue;
		}
	}
	res.sendStatus(200);
});

// recommended to inject access tokens as environmental variables, e.g.
const token = process.env.PAGE_ACCESS_TOKEN;
//const token = "<PAGE_ACCESS_TOKEN>"

function sendTextMessage(sender, text) {
	let messageData = { text:text };

	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

function sendGenericMessage(sender) {
	let messageData = {
		"attachment": {
			"type": "template",
			"payload": {
				"template_type": "generic",
				"elements": [{
					"title": "First card",
					"subtitle": "Element #1 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/rift.png",
					"buttons": [{
						"type": "web_url",
						"url": "https://www.messenger.com",
						"title": "web url"
					}, {
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for first element in a generic bubble",
					}],
				}, {
					"title": "Second card",
					"subtitle": "Element #2 of an hscroll",
					"image_url": "http://messengerdemo.parseapp.com/img/gearvr.png",
					"buttons": [{
						"type": "postback",
						"title": "Postback",
						"payload": "Payload for second element in a generic bubble",
					}],
				}]
			}
		}
	};
	request({
		url: 'https://graph.facebook.com/v2.6/me/messages',
		qs: {access_token:token},
		method: 'POST',
		json: {
			recipient: {id:sender},
			message: messageData,
		}
	}, function(error, response, body) {
		if (error) {
			console.log('Error sending messages: ', error);
		} else if (response.body.error) {
			console.log('Error: ', response.body.error);
		}
	});
}

// spin spin sugar
app.listen(app.get('port'), function() {
	console.log('running on port', app.get('port'));
});
<|endoftext|>