<commit_before>'use strict';
var _ = require('lodash');
var uuid = require('node-uuid');
var joinUrl = require('../util/joinUrl');
var moment = require('moment');

var Post = require('./Post');
var Page = require('./Page');
var Series = require('./Series');
var Paginator = require('./Paginator');

module.exports = function (opts) {

  var postId = 0;

  var defaults = {
    siteTitle: '<TODO: place the name of your site here>',
    blogSlugFormat: '/:year/:month/:title/',
    categoriesSlugFormat: '/blog/categories/{category}/',
    root_url: 'http://example.com',
    author: {
      name: '(TODO put your name ex: Bugs Bunny)',
      email: '', //optional email
      link: '', // optional
    },
    year: new Date().getFullYear().toString(),
    titlecase: false,
    assets: '',
    page_meta: true,

    showUnPublished: false,

    pagination: {
      size: 10,
    },

    subscribe_email: '',

    simple_search: 'https://www.google.com/search',

    sidebar_collapse: false, // TODO: test this.
    sidebar: true,

    recentPostsConfig: {
      show: true,
      showCount: 7
    },

    archives: {
      link: '/blog/archives/',
      label: 'Archives'
    },

    github: {
      username: '',
      repoCount: 5,
      skipForks: true,
      showProfileLink: false,
    },

    twitter: {
      username: '',
      widgetId: '',
      tweetButton: false,
      followButton: false,
    },

    facebook: {
      likeButton: false,
      appId: '',
    },

    google: {
      username: '',
      plusOneButton: false,
      plusOneSize: 'medium',

      analyticsTrackingId: '',
    },

    disqus: {
      show: true,
      shortName: '',
    },

    pages: [],
    seriesList: {}
  };

  var Site = function () {
    this.siteId = uuid.v4();
    this._posts = [];
  };
  Site.prototype = _.merge(defaults, opts);

  Site.prototype.addFile = function (post) {
    ++postId;

    // TODO:
    // guard against someone calling this with duplicate files (from gulp selector selectors)...
    // (I did it while building this and cauzed some crazy bugs).

    if (post.layout === 'none') {
      return _.create(site, post);
    }

    if (post.layout === 'page' || post.layout === 'default') {
      post = new Page(this, post);
      this.pages.push(post);
    } else {
      post = new Post(this, post);
      this.addPost(post);
    }

    post.___postId = postId;

    if (post.isSeries && post.isSeries()) {

      var series = this.seriesList[post.seriesId];
      if (!series) {
        series = new Series(post.seriesAbout);
        this.seriesList[post.seriesId] = series;
      }

      series.about = series.about || post.seriesAbout;
      post.series = series;

      if (series.posts.filter(function (p) { return p.seriesTitle === post.seriesTitle; }).length) {
        console.log('File:', post);
        throw 'Cannot add post duplicate series post - check your grunt configuration.';
      }

      //console.log('pushing:', post.seriesTitle, post.getSiteId());
      series.posts.push(post);

      ++series.totalCount;
      if (post.published === true) {
        ++series.publishedCount;
      }

      //console.log({
      //  'SERIES: ': post.seriesId,
      //  'TITLE: ': post.title,
      //})
    }
    return post;
  };

  Site.prototype.getSiteId = function () {
    return this.siteId;
  };

  Site.prototype.addPost = function (post) {
    if (post) {
      this._posts.push(post);
    }
  };
  Site.prototype.filterPosts = function (posts) {

    var filtered;

    if (this.showUnPublished) {
      filtered = posts;
    } else {
      filtered = posts.filter(function (post) {
        // only exclude posts that are explicitly 'published: false'
        return !(post.published === false); // jshint ignore:line
      });
    }

    return _.sortBy(filtered, _.property('date')).reverse();
  };

  // TODO: recall what enumerable/configurable do...
  Object.defineProperty(Site.prototype, 'recentPosts', {
    get: function () {
      return _.take(this.posts, this.recentPostsConfig.showCount);
    },
    enumerable: true,
    configurable: true
  });

  Object.defineProperty(Site.prototype, 'postsGroupedByYear', {
    get: function () {
      var posts = {};

      this.posts.forEach(function (post) {
        if (!post.date) {
          console.log(post);
          throw 'post missing date property';
        }
        var year = post.date.year();
        var obj = posts[year];
        if (!obj){
          obj = posts[year] = [];
        }

        obj.push(post);
      });

      return Object.keys(posts).sort().reverse().map(function (key) {
        return {
          year: key,
          posts: this.filterPosts(posts[key])
        };
      }.bind(this));
    },
    enumerable: true,
    configurable: true
  });

  Site.prototype.getCategories = function () {
    var categories = {};
    var that = this;

    // this is funky
    // but we're returning a collection of categories (that have a collection of categoryPosts) (cat)
    // where each category

    this.posts.forEach(function (post) {
      (post.categories || []).forEach(function (category) {

        var key = category.name.toLowerCase();
        var cat = categories[key];
        if (cat === undefined) {
          cat = categories[key] = _.create(that, {
            name: category.name,
            file: category.file
        });
          cat.categoryPosts = [];
        }

        cat.categoryPosts.push(post);

        // order the posts by date
        cat.categoryPosts = that.filterPosts(cat.categoryPosts);
      });
    });

    var cats = Object.keys(categories).map(function (key) {
      return categories[key];
    });
    cats.paginator = new Paginator(that, cats);
    return _.sortBy(cats, 'name');
  };

  Object.defineProperty(Site.prototype, 'categories', {
    get: function () {
      return this.getCategories();
    },
    enumerable: true,
    configurable: true
  });

  //// TODO: recall what enumerable/configurable do...
  Object.defineProperty(Site.prototype, 'posts', {
    get: function () {
      return this.filterPosts(this._posts);
    },
    enumerable: true,
    configurable: true
  });

  Object.defineProperty(Site.prototype, 'fullUrl', {
    get: function () {
      var result = joinUrl(this.site.url, this.url);
      return result;
    },
    enumerable: true,
    configurable: true
  });

  Object.defineProperty(Site.prototype, 'date', {
    get: function () {

      if (this._date) {

        var md = moment(this._date);

        if (md.isValid()) {
          return md;
        } else {
          console.log('this._date could not be parsed by momentjs date[' + this._date + '] for[' + this.title + ']');
          return null;
        }
      } else {
        //console.log('this._date is undefined for [' + this.title + ']');
        return null;
      }

    },
    set: function (value) {
      this._date = value;
    },
    enumerable: true,
    configurable: true
  });

  var site = new Site();

  Site.prototype.homePaginator = new Paginator({
    site: site,
    posts: site._posts
  });

  site.Post = Post;
  site.Series = Series;

  return site;
};
<commit_msg>guard against empty category names<commit_after>'use strict';
var _ = require('lodash');
var uuid = require('node-uuid');
var joinUrl = require('../util/joinUrl');
var moment = require('moment');

var Post = require('./Post');
var Page = require('./Page');
var Series = require('./Series');
var Paginator = require('./Paginator');

module.exports = function (opts) {

  var postId = 0;

  var defaults = {
    siteTitle: '<TODO: place the name of your site here>',
    blogSlugFormat: '/:year/:month/:title/',
    categoriesSlugFormat: '/blog/categories/{category}/',
    root_url: 'http://example.com',
    author: {
      name: '(TODO put your name ex: Bugs Bunny)',
      email: '', //optional email
      link: '', // optional
    },
    year: new Date().getFullYear().toString(),
    titlecase: false,
    assets: '',
    page_meta: true,

    showUnPublished: false,

    pagination: {
      size: 10,
    },

    subscribe_email: '',

    simple_search: 'https://www.google.com/search',

    sidebar_collapse: false, // TODO: test this.
    sidebar: true,

    recentPostsConfig: {
      show: true,
      showCount: 7
    },

    archives: {
      link: '/blog/archives/',
      label: 'Archives'
    },

    github: {
      username: '',
      repoCount: 5,
      skipForks: true,
      showProfileLink: false,
    },

    twitter: {
      username: '',
      widgetId: '',
      tweetButton: false,
      followButton: false,
    },

    facebook: {
      likeButton: false,
      appId: '',
    },

    google: {
      username: '',
      plusOneButton: false,
      plusOneSize: 'medium',

      analyticsTrackingId: '',
    },

    disqus: {
      show: true,
      shortName: '',
    },

    pages: [],
    seriesList: {}
  };

  var Site = function () {
    this.siteId = uuid.v4();
    this._posts = [];
  };
  Site.prototype = _.merge(defaults, opts);

  Site.prototype.addFile = function (post) {
    ++postId;

    // TODO:
    // guard against someone calling this with duplicate files (from gulp selector selectors)...
    // (I did it while building this and cauzed some crazy bugs).

    if (post.layout === 'none') {
      return _.create(site, post);
    }

    if (post.layout === 'page' || post.layout === 'default') {
      post = new Page(this, post);
      this.pages.push(post);
    } else {
      post = new Post(this, post);
      this.addPost(post);
    }

    post.___postId = postId;

    if (post.isSeries && post.isSeries()) {

      var series = this.seriesList[post.seriesId];
      if (!series) {
        series = new Series(post.seriesAbout);
        this.seriesList[post.seriesId] = series;
      }

      series.about = series.about || post.seriesAbout;
      post.series = series;

      if (series.posts.filter(function (p) { return p.seriesTitle === post.seriesTitle; }).length) {
        console.log('File:', post);
        throw 'Cannot add post duplicate series post - check your grunt configuration.';
      }

      //console.log('pushing:', post.seriesTitle, post.getSiteId());
      series.posts.push(post);

      ++series.totalCount;
      if (post.published === true) {
        ++series.publishedCount;
      }

      //console.log({
      //  'SERIES: ': post.seriesId,
      //  'TITLE: ': post.title,
      //})
    }
    return post;
  };

  Site.prototype.getSiteId = function () {
    return this.siteId;
  };

  Site.prototype.addPost = function (post) {
    if (post) {
      this._posts.push(post);
    }
  };
  Site.prototype.filterPosts = function (posts) {

    var filtered;

    if (this.showUnPublished) {
      filtered = posts;
    } else {
      filtered = posts.filter(function (post) {
        // only exclude posts that are explicitly 'published: false'
        return !(post.published === false); // jshint ignore:line
      });
    }

    return _.sortBy(filtered, _.property('date')).reverse();
  };

  // TODO: recall what enumerable/configurable do...
  Object.defineProperty(Site.prototype, 'recentPosts', {
    get: function () {
      return _.take(this.posts, this.recentPostsConfig.showCount);
    },
    enumerable: true,
    configurable: true
  });

  Object.defineProperty(Site.prototype, 'postsGroupedByYear', {
    get: function () {
      var posts = {};

      this.posts.forEach(function (post) {
        if (!post.date) {
          console.log(post);
          throw 'post missing date property';
        }
        var year = post.date.year();
        var obj = posts[year];
        if (!obj){
          obj = posts[year] = [];
        }

        obj.push(post);
      });

      return Object.keys(posts).sort().reverse().map(function (key) {
        return {
          year: key,
          posts: this.filterPosts(posts[key])
        };
      }.bind(this));
    },
    enumerable: true,
    configurable: true
  });

  Site.prototype.getCategories = function () {
    var categories = {};
    var that = this;

    // this is funky
    // but we're returning a collection of categories (that have a collection of categoryPosts) (cat)
    // where each category

    this.posts.forEach(function (post) {
      (post.categories || []).forEach(function (category) {

        if(!category.name) {
          throw "Do you have an empty category name in post: " + post.title;
        }

        var key = category.name.toLowerCase();
        var cat = categories[key];
        if (cat === undefined) {
          cat = categories[key] = _.create(that, {
            name: category.name,
            file: category.file
        });
          cat.categoryPosts = [];
        }

        cat.categoryPosts.push(post);

        // order the posts by date
        cat.categoryPosts = that.filterPosts(cat.categoryPosts);
      });
    });

    var cats = Object.keys(categories).map(function (key) {
      return categories[key];
    });
    cats.paginator = new Paginator(that, cats);
    return _.sortBy(cats, 'name');
  };

  Object.defineProperty(Site.prototype, 'categories', {
    get: function () {
      return this.getCategories();
    },
    enumerable: true,
    configurable: true
  });

  //// TODO: recall what enumerable/configurable do...
  Object.defineProperty(Site.prototype, 'posts', {
    get: function () {
      return this.filterPosts(this._posts);
    },
    enumerable: true,
    configurable: true
  });

  Object.defineProperty(Site.prototype, 'fullUrl', {
    get: function () {
      var result = joinUrl(this.site.url, this.url);
      return result;
    },
    enumerable: true,
    configurable: true
  });

  Object.defineProperty(Site.prototype, 'date', {
    get: function () {

      if (this._date) {

        var md = moment(this._date);

        if (md.isValid()) {
          return md;
        } else {
          console.log('this._date could not be parsed by momentjs date[' + this._date + '] for[' + this.title + ']');
          return null;
        }
      } else {
        //console.log('this._date is undefined for [' + this.title + ']');
        return null;
      }

    },
    set: function (value) {
      this._date = value;
    },
    enumerable: true,
    configurable: true
  });

  var site = new Site();

  Site.prototype.homePaginator = new Paginator({
    site: site,
    posts: site._posts
  });

  site.Post = Post;
  site.Series = Series;

  return site;
};
<|endoftext|>


<commit_before>import React from 'react';
import {
  View,
  TouchableOpacity
} from 'react-native';

class DropZone extends React.Component {
    constructor(props) {
        super(props);
        this.displayName = 'DropZone';
        this.state = {};
        this.reportMeasurements = this.reportMeasurements.bind(this);
        this.onEnter = this.onEnter.bind(this);
        this.onLeave = this.onLeave.bind(this);
        this.onDrop = this.onDrop.bind(this);
    }

    reportMeasurements() {
      this.refs.wrapper.measure((_, __, width, height, x, y) => {
        this.context.dragContext.updateZone({width, height, x, y, ref: this.refs.wrapper, onEnter: this.onEnter, onLeave: this.onLeave, onDrop: this.onDrop});
      })
    }

    static propTypes = {
      onEnter: React.PropTypes.func,
      onLeave: React.PropTypes.func,
      onDrop: React.PropTypes.func,
    }

    componentDidMount() {
      this.reportMeasurements();
      this._timer = setInterval(this.reportMeasurements, 1000);
    }

    componentWillUnmount() {
      this.context.dragContext.removeZone(this.refs.wrapper);
      clearInterval(this._timer);
    }
    componentDidUpdate() {
      this.reportMeasurements();
    }

    onEnter({x, y}) {
      if (this.props.disabled) return;
      if (!this.state.active) {
        if (this.props.onEnter) this.props.onEnter();
        this.setState({
          active: true
        })
      }
    }

    onLeave() {
      if (this.props.disabled) return;
      if (this.state.active) {
        if (this.props.onLeave) this.props.onLeave();
        this.setState({
          active: false
        })
      }
    }

    onDrop(data) {
      if (this.props.disabled) return;
      if (this.props.onDrop) this.props.onDrop(data);
      this.setState({
        active: false
      })
    }

    static contextTypes = {
      dragContext: React.PropTypes.any
    }

    render() {
        return <View style={this.props.style} pointerEvents={this.props.pointerEvents} onLayout={this.reportMeasurements} ref="wrapper">
          {
            React.Children.map(this.props.children, child => {
              return React.cloneElement(child, Object.assign({}, this.props, {dragOver: this.state.active}));
            })
          }
        </View>;
    }
}

export default DropZone;
<commit_msg>stop dropzones from registering when their being dragged<commit_after>import React from 'react';
import {
  View,
  TouchableOpacity
} from 'react-native';

class DropZone extends React.Component {
    constructor(props) {
        super(props);
        this.displayName = 'DropZone';
        this.state = {};
        this.reportMeasurements = this.reportMeasurements.bind(this);
        this.onEnter = this.onEnter.bind(this);
        this.onLeave = this.onLeave.bind(this);
        this.onDrop = this.onDrop.bind(this);
    }

    reportMeasurements() {
      if (this.props.dragging) this.context.dragContext.removeZone(this.refs.wrapper);
      this.refs.wrapper.measure((_, __, width, height, x, y) => {
        if (!this.props.dragging) this.context.dragContext.updateZone({width, height, x, y, ref: this.refs.wrapper, onEnter: this.onEnter, onLeave: this.onLeave, onDrop: this.onDrop});
      })
    }

    static propTypes = {
      onEnter: React.PropTypes.func,
      onLeave: React.PropTypes.func,
      onDrop: React.PropTypes.func,
    }

    componentDidMount() {
      this.reportMeasurements();
      this._timer = setInterval(this.reportMeasurements, 1000);
    }

    componentWillUnmount() {
      this.context.dragContext.removeZone(this.refs.wrapper);
      clearInterval(this._timer);
    }
    componentDidUpdate() {
      this.reportMeasurements();
    }

    onEnter({x, y}) {
      if (this.props.disabled) return;
      if (!this.state.active) {
        if (this.props.onEnter) this.props.onEnter();
        this.setState({
          active: true
        })
      }
    }

    onLeave() {
      if (this.props.disabled) return;
      if (this.state.active) {
        if (this.props.onLeave) this.props.onLeave();
        this.setState({
          active: false
        })
      }
    }

    onDrop(data) {
      if (this.props.disabled) return;
      if (this.props.onDrop) this.props.onDrop(data);
      this.setState({
        active: false
      })
    }

    static contextTypes = {
      dragContext: React.PropTypes.any
    }

    render() {
        return <View style={this.props.style} pointerEvents={this.props.pointerEvents} onLayout={this.reportMeasurements} ref="wrapper">
          {
            React.Children.map(this.props.children, child => {
              return React.cloneElement(child, Object.assign({}, this.props, {dragOver: this.state.active}));
            })
          }
        </View>;
    }
}

export default DropZone;
<|endoftext|>


<commit_before>var request = require('request')
var _ = require('lodash')

var ejson = require('@carbon-io/ejson')
var HttpErrors = require('@carbon-io/http-errors')

var Collection = require('./Collection')

DEFAULT_OPTIONS = {
  json: true,
  strictSSL: true
}

/***************************************************************************************************
 * @class Endpoint
 * @description This is a private class. Should not call Endpoint constructor directly,
 *              but should use RestClient.getEndpoint(path)
 * @memberof carbon-client
 */
function Endpoint() {
  /*****************************************************************************
   * @property {xxx} client -- xxx
   */
  this.client = null
  /*****************************************************************************
   * @property {string} path -- xxx
   */
  this.path = null
  /*****************************************************************************
   * @property {xxx} parent -- xxx
   */
  this.parent = null
  /*****************************************************************************
   * @property {xxx} endpoints -- xxx
   */
  this.endpoints = {}
}

/***************************************************************************************************
 * collection class class variable
 *
 * @static
 *
 * @type {Collection|*|exports}
 */

Endpoint.collectionClass = Collection

/***************************************************************************************************
 * @method getAbsolutePath
 * @description getAbsolutePath description
 * returns {xxx} -- xxx
 */
Endpoint.prototype.getAbsolutePath = function() {
  if (!this.parent) {
    return '/'
  }

  var parentPath = this.parent.getAbsolutePath()
  if (parentPath === '/') {
    parentPath = ''
  }

  return parentPath + '/' + (this.path ? this.path : '')
}

/***************************************************************************************************
 * @method getFullUrl
 * @description getFullUrl description
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.getFullUrl = function() {
  var absolutePath = this.getAbsolutePath()
  if (absolutePath === '/') {
    absolutePath = ''
  }
  return this.client.url + absolutePath
}

/***************************************************************************************************
 * @method getEndpoint
 * @description getEndpoint description
 * @param {string} path -- (i.e. 'foo' or 'foo/bar')
 * @returns {xxx}
 */
Endpoint.prototype.getEndpoint = function(path) {
  if (!path) {
    return null // XXX null or this?
  }

  var pathList = path.split('/')
  if (pathList.length === 0) { /// XXX null or this?
    return null
  }

  if (pathList[0] === '') {
    // means path started with '/' XXX maybe we should not trim here and go
    // rel to root?
    pathList.shift()
  }

  if (pathList[pathList.length - 1] === '') { // means path ended with '/'
    pathList.pop()
  }

  return this._getEndpointFromPathList(pathList)
}

/***************************************************************************************************
 * @method getCollection
 * @description getCollection description
 * @param {string} path -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.getCollection = function(path) {
  var result = undefined

  var endpoint = this.getEndpoint(path)
  if (endpoint) {
    result = new Collection(endpoint)
  }

  return result
}

/***************************************************************************************************
 * @method get
 * @description Supported calling forms:
 *              get(cb)
 *              get(options, cb
 * @param {Object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.get = function() {
  return this._performOperation('get', arguments)
}

/***************************************************************************************************
 * @method post
 * @description Supported calling forms:
 *              post(cb)
 *              post(body, cb)
 *              post(body, options, cb)
 * @param {xxx} body -- (optional)
 * @param {xxx} options -- (optional)
 * @param {xxx} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.post = function() {
  return this._performOperation('post', arguments)
}

/***************************************************************************************************
 * @method put
 * @description Supported calling forms:
 *              put(cb)
 *              put(body, cb)
 *              put(body, options, cb)
 * @param {object} body -- (optional)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.put = function() {
  return this._performOperation('put', arguments)
}

/***************************************************************************************************
 * @method patch
 * @description Supported calling forms:
 *              patch(cb)
 *              patch(body, cb)
 *              patch(body, options, cb)
 * @param {object} body -- (optional)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.patch = function() {
  return this._performOperation('patch', arguments)
}

/***************************************************************************************************
 * @method delete
 * @description Supported calling forms:
 *              delete(cb)
 *              delete(options, cb)
 * @param {oject} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.delete = function() {
  return this._performOperation('delete', arguments)
}

/***************************************************************************************************
 * @method head
 * @description Supported calling forms:
 *              head(cb)
 *              head(options, cb)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.head = function() {
  return this._performOperation('head', arguments)
}

/***************************************************************************************************
 * @method options
 * @description Supported calling forms:
 *              options(cb)
 *              options(options, cb)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.options = function() {
  return this._performOperation('options', arguments)
}

/***************************************************************************************************
 * @method httpError
 * @description xxx
 * @param {xxx} code -- xxx
 * @param {xxx} message -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.httpError = function(code, message) {
  var errorClass = this._getHttpErrorByCode(code)
  if (!errorClass) {
    errorClass = Error
  }

  var error = new errorClass(message)
  return error
}

/***************************************************************************************************
 * @method  _performOperation
 * @description _performOperation description
 * @param {Function} method -- xxx
 * @param {object} operationArguments -- xxx
 * @returns {undefined} -- undefined
 */
Endpoint.prototype._performOperation = function(method, operationArguments) {
  var body = undefined
  var options = {}
  var cb = undefined

  // Figure out what the arguments are
  switch (operationArguments.length) {
    case 3:
      body = operationArguments[0]
      options = operationArguments[1]
      cb = operationArguments[2]
      break
    case 2:
      if (['post', 'put', 'patch'].indexOf(method.toLowerCase()) !== -1) {
        body = operationArguments[0]
      } else {
        options = operationArguments[0]
      }
      cb = operationArguments[1]
      break
    case 1:
      cb = operationArguments[0]
      break
    default:
  }

  // apply default options
  options = this._applyDefaultOptions(options)

  // use ejson to serialize body so that extended types are preserved
  if(options.json && body) {
    try {
      body = ejson.serialize(body)
    } catch(e) {
      // ignore
    }
  }

  var parameters = options.parameters || options.params
  // Execute the request
  var self = this
  // XXX should we allow the user to influence redirect behavior?
  request({
    url: this.getFullUrl(),
    method: method.toUpperCase(),
    // qs: note that we serialize the json parameters to avoid wrong type conversions on server-side
    // see https://github.com/ljharb/qs/issues/91
    qs: serializeJsonParams(parameters),
    body: body,
    json: options.json,
    headers: options.headers,
    strictSSL: options.strictSSL,
    cert: options.cert,
    key: options.key,
    ca: options.ca,
    forever: options.forever,
    timeout: options.timeout
  }, function(e, r, b) {
    // use ejson to deserialize response body
    if(options.json) {
      try {
        r.body = ejson.deserialize(r.body)
      } catch(e) {
        // ignore
      }
    }
    // redirects are handled by request, so no need to
    if (e) {
      cb(self.httpError(e.code, e.message || e), r)
    } else if (r.statusCode >= 400) {
      cb(self.httpError(r.statusCode, r.body.message || r.body.description), r)
    } else {
      cb(null, r)
    }
  })
}

/***************************************************************************************************
 * @method _getEndpointFromPathList
 * @description _getEndpointFromPathList description
 * @param {object} pathList -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._getEndpointFromPathList = function(pathList) {
  // get child Endpoint
  var child = this._getChild(pathList.shift())

  // recurse if there is path left
  if (pathList.length > 0) {
    return child._getEndpointFromPathList(pathList)
  } else {
    return child
  }
}

/***************************************************************************************************
 * @method _applyDefaultOptions
 * @description applies client's default options to the specified options
 * @param {object} options -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._applyDefaultOptions = function(options) {
  var mergedOptions = _.cloneDeep(options)
  var defaultOptions = _.cloneDeep(DEFAULT_OPTIONS)
  if (this.client) {
    mergedOptions = _.merge(defaultOptions,
      this.client.defaultOptions,
      mergedOptions)  // give preference to options passed
    // to `_performOperation`
  }

  return mergedOptions
}

/***************************************************************************************************
 * @method _getChild
 * @description _getChild description
 * @param {string} path -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._getChild = function(path) {
  if (!this.endpoints[path]) {
    var newEndpoint = new Endpoint()
    newEndpoint.path = path
    newEndpoint.parent = this
    newEndpoint.client = this.client

    this.endpoints[path] = newEndpoint
  }

  return this.endpoints[path]
}

/***************************************************************************************************
 * @method _httpErrorsByCode
 * @description Attached to prototype and shared by all Endpoints
 * @returns {undefined}
 */
Endpoint.prototype._httpErrorsByCode = {}

/***************************************************************************************************
 * @method _getHttpErrorByCode
 * @description _getHttpErroByCode description
 * @param {xxx} code -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._getHttpErrorByCode = function(code) {
  return this._httpErrorsByCode[code]
}

/***************************************************************************************************
 * static init
 */
for (var key in HttpErrors) {
  var e = HttpErrors[key]
  Endpoint.prototype._httpErrorsByCode[e.code] = e
}

/***************************************************************************************************
 * @method serializeJsonParams
 * @param {object} params -- xxx
 * @returns {object} -- a new object with params json values serialized as json strings
 *
 */
function serializeJsonParams(params) {

  var result = {}
  if (params) {
    for (var key in params) {
      if (_.isObject(params[key])) {
        result[key] = ejson.stringify(params[key])
      } else {
        result[key] = params[key]
      }
    }
  }

  return result
}

/******************************************************************************
 * exports
 */
module.exports = Endpoint

<commit_msg>update per http-errors changes: include response in error + use code mapping<commit_after>var request = require('request')
var _ = require('lodash')

var ejson = require('@carbon-io/ejson')
var HttpErrors = require('@carbon-io/http-errors')

var Collection = require('./Collection')

DEFAULT_OPTIONS = {
  json: true,
  strictSSL: true
}

/***************************************************************************************************
 * @class Endpoint
 * @description This is a private class. Should not call Endpoint constructor directly,
 *              but should use RestClient.getEndpoint(path)
 * @memberof carbon-client
 */
function Endpoint() {
  /*****************************************************************************
   * @property {xxx} client -- xxx
   */
  this.client = null
  /*****************************************************************************
   * @property {string} path -- xxx
   */
  this.path = null
  /*****************************************************************************
   * @property {xxx} parent -- xxx
   */
  this.parent = null
  /*****************************************************************************
   * @property {xxx} endpoints -- xxx
   */
  this.endpoints = {}
}

/***************************************************************************************************
 * collection class class variable
 *
 * @static
 *
 * @type {Collection|*|exports}
 */

Endpoint.collectionClass = Collection

/***************************************************************************************************
 * @method getAbsolutePath
 * @description getAbsolutePath description
 * returns {xxx} -- xxx
 */
Endpoint.prototype.getAbsolutePath = function() {
  if (!this.parent) {
    return '/'
  }

  var parentPath = this.parent.getAbsolutePath()
  if (parentPath === '/') {
    parentPath = ''
  }

  return parentPath + '/' + (this.path ? this.path : '')
}

/***************************************************************************************************
 * @method getFullUrl
 * @description getFullUrl description
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.getFullUrl = function() {
  var absolutePath = this.getAbsolutePath()
  if (absolutePath === '/') {
    absolutePath = ''
  }
  return this.client.url + absolutePath
}

/***************************************************************************************************
 * @method getEndpoint
 * @description getEndpoint description
 * @param {string} path -- (i.e. 'foo' or 'foo/bar')
 * @returns {xxx}
 */
Endpoint.prototype.getEndpoint = function(path) {
  if (!path) {
    return null // XXX null or this?
  }

  var pathList = path.split('/')
  if (pathList.length === 0) { /// XXX null or this?
    return null
  }

  if (pathList[0] === '') {
    // means path started with '/' XXX maybe we should not trim here and go
    // rel to root?
    pathList.shift()
  }

  if (pathList[pathList.length - 1] === '') { // means path ended with '/'
    pathList.pop()
  }

  return this._getEndpointFromPathList(pathList)
}

/***************************************************************************************************
 * @method getCollection
 * @description getCollection description
 * @param {string} path -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.getCollection = function(path) {
  var result = undefined

  var endpoint = this.getEndpoint(path)
  if (endpoint) {
    result = new Collection(endpoint)
  }

  return result
}

/***************************************************************************************************
 * @method get
 * @description Supported calling forms:
 *              get(cb)
 *              get(options, cb
 * @param {Object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.get = function() {
  return this._performOperation('get', arguments)
}

/***************************************************************************************************
 * @method post
 * @description Supported calling forms:
 *              post(cb)
 *              post(body, cb)
 *              post(body, options, cb)
 * @param {xxx} body -- (optional)
 * @param {xxx} options -- (optional)
 * @param {xxx} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.post = function() {
  return this._performOperation('post', arguments)
}

/***************************************************************************************************
 * @method put
 * @description Supported calling forms:
 *              put(cb)
 *              put(body, cb)
 *              put(body, options, cb)
 * @param {object} body -- (optional)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.put = function() {
  return this._performOperation('put', arguments)
}

/***************************************************************************************************
 * @method patch
 * @description Supported calling forms:
 *              patch(cb)
 *              patch(body, cb)
 *              patch(body, options, cb)
 * @param {object} body -- (optional)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.patch = function() {
  return this._performOperation('patch', arguments)
}

/***************************************************************************************************
 * @method delete
 * @description Supported calling forms:
 *              delete(cb)
 *              delete(options, cb)
 * @param {oject} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.delete = function() {
  return this._performOperation('delete', arguments)
}

/***************************************************************************************************
 * @method head
 * @description Supported calling forms:
 *              head(cb)
 *              head(options, cb)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.head = function() {
  return this._performOperation('head', arguments)
}

/***************************************************************************************************
 * @method options
 * @description Supported calling forms:
 *              options(cb)
 *              options(options, cb)
 * @param {object} options -- (optional)
 * @param {Function} cb -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.options = function() {
  return this._performOperation('options', arguments)
}

/***************************************************************************************************
 * @method httpError
 * @description xxx
 * @param {xxx} code -- xxx
 * @param {xxx} message -- xxx
 * @param {xxx} response -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype.httpError = function(code, message, response) {
  var errorClass = HttpErrors.codes[code]
  if (!errorClass) {
    errorClass = Error
  }

  var error = new errorClass(message, response)
  return error
}

/***************************************************************************************************
 * @method  _performOperation
 * @description _performOperation description
 * @param {Function} method -- xxx
 * @param {object} operationArguments -- xxx
 * @returns {undefined} -- undefined
 */
Endpoint.prototype._performOperation = function(method, operationArguments) {
  var body = undefined
  var options = {}
  var cb = undefined

  // Figure out what the arguments are
  switch (operationArguments.length) {
    case 3:
      body = operationArguments[0]
      options = operationArguments[1]
      cb = operationArguments[2]
      break
    case 2:
      if (['post', 'put', 'patch'].indexOf(method.toLowerCase()) !== -1) {
        body = operationArguments[0]
      } else {
        options = operationArguments[0]
      }
      cb = operationArguments[1]
      break
    case 1:
      cb = operationArguments[0]
      break
    default:
  }

  // apply default options
  options = this._applyDefaultOptions(options)

  // use ejson to serialize body so that extended types are preserved
  if(options.json && body) {
    try {
      body = ejson.serialize(body)
    } catch(e) {
      // ignore
    }
  }

  var parameters = options.parameters || options.params
  // Execute the request
  var self = this
  // XXX should we allow the user to influence redirect behavior?
  request({
    url: this.getFullUrl(),
    method: method.toUpperCase(),
    // qs: note that we serialize the json parameters to avoid wrong type conversions on server-side
    // see https://github.com/ljharb/qs/issues/91
    qs: serializeJsonParams(parameters),
    body: body,
    json: options.json,
    headers: options.headers,
    strictSSL: options.strictSSL,
    cert: options.cert,
    key: options.key,
    ca: options.ca,
    forever: options.forever,
    timeout: options.timeout
  }, function(e, r, b) {
    // use ejson to deserialize response body
    if(options.json) {
      try {
        r.body = ejson.deserialize(r.body)
      } catch(e) {
        // ignore
      }
    }
    // redirects are handled by request, so no need to
    if (e) {
      var msg = e.message || JSON.stringify(e)
      cb(self.httpError(e.code, msg, r), r)
    } else if (r.statusCode >= 400) {
      var msg = r.body.message || JSON.stringify(r.body)
      cb(self.httpError(r.statusCode, msg, r), r)
    } else {
      cb(null, r)
    }
  })
}

/***************************************************************************************************
 * @method _getEndpointFromPathList
 * @description _getEndpointFromPathList description
 * @param {object} pathList -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._getEndpointFromPathList = function(pathList) {
  // get child Endpoint
  var child = this._getChild(pathList.shift())

  // recurse if there is path left
  if (pathList.length > 0) {
    return child._getEndpointFromPathList(pathList)
  } else {
    return child
  }
}

/***************************************************************************************************
 * @method _applyDefaultOptions
 * @description applies client's default options to the specified options
 * @param {object} options -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._applyDefaultOptions = function(options) {
  var mergedOptions = _.cloneDeep(options)
  var defaultOptions = _.cloneDeep(DEFAULT_OPTIONS)
  if (this.client) {
    mergedOptions = _.merge(defaultOptions,
      this.client.defaultOptions,
      mergedOptions)  // give preference to options passed
    // to `_performOperation`
  }

  return mergedOptions
}

/***************************************************************************************************
 * @method _getChild
 * @description _getChild description
 * @param {string} path -- xxx
 * @returns {xxx} -- xxx
 */
Endpoint.prototype._getChild = function(path) {
  if (!this.endpoints[path]) {
    var newEndpoint = new Endpoint()
    newEndpoint.path = path
    newEndpoint.parent = this
    newEndpoint.client = this.client

    this.endpoints[path] = newEndpoint
  }

  return this.endpoints[path]
}

/***************************************************************************************************
 * @method serializeJsonParams
 * @param {object} params -- xxx
 * @returns {object} -- a new object with params json values serialized as json strings
 *
 */
function serializeJsonParams(params) {

  var result = {}
  if (params) {
    for (var key in params) {
      if (_.isObject(params[key])) {
        result[key] = ejson.stringify(params[key])
      } else {
        result[key] = params[key]
      }
    }
  }

  return result
}

/******************************************************************************
 * exports
 */
module.exports = Endpoint

<|endoftext|>


<commit_before>var Botkit = require(__dirname + '/CoreBot.js');
var request = require('request');
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto')

function Facebookbot(configuration) {

    // Create a core botkit bot
    var facebook_botkit = Botkit(configuration || {});

    if (facebook_botkit.config.require_delivery) {

        facebook_botkit.on('message_delivered', function(bot, message) {

            // get list of mids in this message
            for (var m = 0; m < message.delivery.mids.length; m++) {
                var mid = message.delivery.mids[m];

                // loop through all active conversations this bot is having
                // and mark messages in conversations as delivered = true
                bot.findConversation(message, function(convo) {
                    if (convo) {
                        for (var s = 0; s < convo.sent.length; s++) {
                            if (convo.sent[s].sent_timestamp <= message.delivery.watermark ||
                                (convo.sent[s].api_response && convo.sent[s].api_response.mid == mid)) {
                                convo.sent[s].delivered = true;
                            }
                        }
                    }
                });
            }

        });

    }

    // customize the bot definition, which will be used when new connections
    // spawn!
    facebook_botkit.defineBot(function(botkit, config) {

        var bot = {
            type: 'fb',
            botkit: botkit,
            config: config || {},
            utterances: botkit.utterances,
        };

        bot.startConversation = function(message, cb) {
            botkit.startConversation(this, message, cb);
        };

        bot.createConversation = function(message, cb) {
            botkit.createConversation(this, message, cb);
        };

        bot.send = function(message, cb) {

            var facebook_message = {
                recipient: {},
                message: message.sender_action ? undefined : {}
            };

            if (typeof(message.channel) == 'string' && message.channel.match(/\+\d+\(\d\d\d\)\d\d\d\-\d\d\d\d/)) {
                facebook_message.recipient.phone_number = message.channel;
            } else {
                facebook_message.recipient.id = message.channel;
            }

            if (!message.sender_action) {
                if (message.text) {
                    facebook_message.message.text = message.text;
                }

                if (message.attachment) {
                    facebook_message.message.attachment = message.attachment;
                }

                if (message.sticker_id) {
                    facebook_message.message.sticker_id = message.sticker_id;
                }

                if (message.quick_replies) {

                    // sanitize the length of the title to maximum of 20 chars
                    var titleLimit = function(title) {
                        if (title.length > 20) {
                            var newTitle = title.substring(0, 16) + '...';
                            return newTitle;
                        } else {
                            return title;
                        }
                    };

                    facebook_message.message.quick_replies = message.quick_replies.map(function(item) {
                        return {
                            content_type: item.content_type || 'text',
                            title: titleLimit(item.title),
                            payload: item.payload,
                            image_url: item.image_url,
                        };
                    });
                }
            } else {
                facebook_message.sender_action = message.sender_action;
            }

            if (message.sender_action) {
                facebook_message.sender_action = message.sender_action;
            }

            if (message.notification_type) {
                facebook_message.notification_type = message.notification_type;
            }

            //Add Access Token to outgoing request
            facebook_message.access_token = configuration.access_token;

            request({
                method: 'POST',
                json: true,
                headers: {
                    'content-type': 'application/json',
                },
                body: facebook_message,
                uri: 'https://graph.facebook.com/v2.6/me/messages'
            },
                function(err, res, body) {

                    if (err) {
                        botkit.debug('WEBHOOK ERROR', err);
                        return cb && cb(err);
                    }

                    if (body.error) {
                        botkit.debug('API ERROR', body.error);
                        return cb && cb(body.error.message);
                    }

                    botkit.debug('WEBHOOK SUCCESS', body);
                    cb && cb(null, body);
                });
        };

        bot.startTyping = function(src, cb) {
            var msg = {};
            msg.channel = src.channel;
            msg.sender_action = 'typing_on';
            bot.say(msg, cb);
        };

        bot.stopTyping = function(src, cb) {
            var msg = {};
            msg.channel = src.channel;
            msg.sender_action = 'typing_off';
            bot.say(msg, cb);
        };

        bot.replyWithTyping = function(src, resp, cb) {
            var text;

            if (typeof(resp) == 'string') {
                text = resp;
            } else {
                text = resp.text;
            }

            var avgWPM = 85;
            var avgCPM = avgWPM * 7;

            var typingLength = Math.min(Math.floor(text.length / (avgCPM / 60)) * 1000, 5000);

            bot.startTyping(src, function(err) {
                if (err) console.log(err);
                setTimeout(function() {
                    bot.reply(src, resp, cb);
                }, typingLength);
            });

        };

        bot.reply = function(src, resp, cb) {
            var msg = {};

            if (typeof(resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }

            msg.channel = src.channel;

            bot.say(msg, cb);
        };

        bot.findConversation = function(message, cb) {
            botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
            for (var t = 0; t < botkit.tasks.length; t++) {
                for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
                    if (
                        botkit.tasks[t].convos[c].isActive() &&
                        botkit.tasks[t].convos[c].source_message.user == message.user
                    ) {
                        botkit.debug('FOUND EXISTING CONVO!');
                        cb(botkit.tasks[t].convos[c]);
                        return;
                    }
                }
            }

            cb();
        };

        return bot;
    });

    // set up a web route for receiving outgoing webhooks and/or slash commands

    facebook_botkit.createWebhookEndpoints = function(webserver, bot, cb) {

        facebook_botkit.log(
            '** Serving webhook endpoints for Messenger Platform at: ' +
            'http://' + facebook_botkit.config.hostname + ':' + facebook_botkit.config.port + '/facebook/receive');

        webserver.post('/facebook/receive', function(req, res) {

            facebook_botkit.debug('GOT A MESSAGE HOOK');
            var obj = req.body;
            if (obj.entry) {
                for (var e = 0; e < obj.entry.length; e++) {
                    for (var m = 0; m < obj.entry[e].messaging.length; m++) {
                        var facebook_message = obj.entry[e].messaging[m];
                        if (facebook_message.message) {
                            var message = {
                                text: facebook_message.message.text,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                                seq: facebook_message.message.seq,
                                is_echo: facebook_message.message.is_echo,
                                mid: facebook_message.message.mid,
                                sticker_id: facebook_message.message.sticker_id,
                                attachments: facebook_message.message.attachments,
                                quick_reply: facebook_message.message.quick_reply,
                                type: 'user_message',
                            };

                            facebook_botkit.receiveMessage(bot, message);
                        } else if (facebook_message.postback) {

                            // trigger BOTH a facebook_postback event
                            // and a normal message received event.
                            // this allows developers to receive postbacks as part of a conversation.
                            var message = {
                                text: facebook_message.postback.payload,
                                payload: facebook_message.postback.payload,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                            };

                            facebook_botkit.trigger('facebook_postback', [bot, message]);

                            if (facebook_botkit.config.receive_via_postback) {
                                var message = {
                                    text: facebook_message.postback.payload,
                                    user: facebook_message.sender.id,
                                    channel: facebook_message.sender.id,
                                    timestamp: facebook_message.timestamp,
                                    type: 'facebook_postback',
                                };

                                facebook_botkit.receiveMessage(bot, message);
                            }

                        } else if (facebook_message.optin) {

                            var message = {
                                optin: facebook_message.optin,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                            };

                            facebook_botkit.trigger('facebook_optin', [bot, message]);
                        } else if (facebook_message.delivery) {

                            var message = {
                                delivery: facebook_message.delivery,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                            };

                            facebook_botkit.trigger('message_delivered', [bot, message]);
                        } else if (facebook_message.read) {

                            var message = {
                                read: facebook_message.read,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                            };

                            facebook_botkit.trigger('message_read', [bot, message]);
                        } else if (facebook_message.referral) {
                            var message = {
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                                referral: facebook_message.referral,
                            };

                            facebook_botkit.trigger('facebook_referral', [bot, message]);
                        }  else {
                            facebook_botkit.log('Got an unexpected message from Facebook: ', facebook_message);
                        }
                    }
                }
            }
            res.send('ok');
        });

        webserver.get('/facebook/receive', function(req, res) {
            if (req.query['hub.mode'] == 'subscribe') {
                if (req.query['hub.verify_token'] == configuration.verify_token) {
                    res.send(req.query['hub.challenge']);
                } else {
                    res.send('OK');
                }
            }

        });

        if (cb) {
            cb();
        }

        return facebook_botkit;
    };

    facebook_botkit.setupWebserver = function(port, cb) {

        if (!port) {
            throw new Error('Cannot start webserver without a port');
        }

        var static_dir =  __dirname + '/public';

        if (facebook_botkit.config && facebook_botkit.config.webserver && facebook_botkit.config.webserver.static_dir)
            static_dir = facebook_botkit.config.webserver.static_dir;

        facebook_botkit.config.port = port;

        facebook_botkit.webserver = express();

        // Validate that requests come from facebook, and abort on validation errors
        if (facebook_botkit.config.validate_requests === true) {
            // Load verify middleware just for post route on our receive webhook, and catch any errors it might throw to prevent the request from being parsed further.
            facebook_botkit.webserver.post('/facebook/receive', bodyParser.json({verify: verifyRequest}))
            facebook_botkit.webserver.use(abortOnValidationError)
        }

        facebook_botkit.webserver.use(bodyParser.json());
        facebook_botkit.webserver.use(bodyParser.urlencoded({ extended: true }));
        facebook_botkit.webserver.use(express.static(static_dir));

        var server = facebook_botkit.webserver.listen(
            facebook_botkit.config.port,
            facebook_botkit.config.hostname,
            function() {
                facebook_botkit.log('** Starting webserver on port ' +
                    facebook_botkit.config.port);
                if (cb) { cb(null, facebook_botkit.webserver); }
            });

        request.post('https://graph.facebook.com/me/subscribed_apps?access_token=' + configuration.access_token,
            function(err, res, body) {
                if (err) {
                    facebook_botkit.log('Could not subscribe to page messages');
                } else {
                    facebook_botkit.debug('Successfully subscribed to Facebook events:', body);
                    facebook_botkit.startTicking();
                }
            });

        return facebook_botkit;

    };

    facebook_botkit.api = {
        'thread_settings': {
            greeting: function(greeting) {
                var message = {
                    'setting_type': 'greeting',
                    'greeting': {
                        'text': greeting
                    }
                };
                facebook_botkit.api.thread_settings.postAPI(message);
            },
            delete_greeting: function() {
                var message = {
                    'setting_type': 'greeting',
                };
                facebook_botkit.api.thread_settings.deleteAPI(message);
            },
            get_started: function(payload) {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'new_thread',
                    'call_to_actions':
                        [
                            {
                                'payload': payload
                            }
                        ]
                };
                facebook_botkit.api.thread_settings.postAPI(message);
            },
            delete_get_started: function() {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'new_thread',
                };
                facebook_botkit.api.thread_settings.deleteAPI(message);
            },
            menu: function(payload) {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'existing_thread',
                    'call_to_actions': payload
                };
                facebook_botkit.api.thread_settings.postAPI(message);
            },
            delete_menu: function() {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'existing_thread',
                };
                facebook_botkit.api.thread_settings.deleteAPI(message);
            },
            postAPI: function(message) {
                request.post('https://graph.facebook.com/v2.6/me/thread_settings?access_token=' + configuration.access_token,
                    {form: message},
                    function(err, res, body) {
                        if (err) {
                            facebook_botkit.log('Could not configure thread settings');
                        } else {

                            var results = null;
                            try {
                                results = JSON.parse(body);
                            } catch (err) {
                                facebook_botkit.log('ERROR in thread_settings API call: Could not parse JSON', err, body);
                            }

                            if (results) {
                                if (results.error) {
                                    facebook_botkit.log('ERROR in thread_settings API call: ', results.error.message);
                                } else {
                                    facebook_botkit.debug('Successfully configured thread settings', body);
                                }
                            }

                        }
                    });
            },
            deleteAPI: function(message) {
                request.delete('https://graph.facebook.com/v2.6/me/thread_settings?access_token=' + configuration.access_token,
                    {form: message},
                    function(err, res, body) {
                        if (err) {
                            facebook_botkit.log('Could not configure thread settings');
                        } else {
                            facebook_botkit.debug('Successfully configured thread settings', message);
                        }
                    });
            }
        }
    };

    // Verifies the SHA1 signature of the raw request payload before bodyParser parses it
    // Will abort parsing if signature is invalid, and pass a generic error to response
    function verifyRequest(req, res, buf, encoding) {
      var expected = req.headers['x-hub-signature'];
      var calculated = getSignature(buf);
      if (expected !== calculated) {
        throw new Error("Invalid signature on incoming request");
      } else {
        facebook_botkit.debug('** X-Hub Verification successful!')
      }
    }

    function getSignature(buf) {
      var hmac = crypto.createHmac("sha1", process.env.fb_app_secret);
      hmac.update(buf, "utf-8");
      return "sha1=" + hmac.digest("hex");
    }

    function abortOnValidationError(err, req, res, next) {
      if (err) {
        facebook_botkit.log('** Invalid X-HUB signature on incoming request!')
        facebook_botkit.debug('** X-HUB Validation Error:', err)
        res.status(400).send({ error: "Invalid signature." });
      } else {
        next();
      }
    }

    return facebook_botkit;
};

module.exports = Facebookbot;
<commit_msg>cleanup<commit_after>var Botkit = require(__dirname + '/CoreBot.js');
var request = require('request');
var express = require('express');
var bodyParser = require('body-parser');
var crypto = require('crypto')

function Facebookbot(configuration) {

    // Create a core botkit bot
    var facebook_botkit = Botkit(configuration || {});

    if (facebook_botkit.config.require_delivery) {

        facebook_botkit.on('message_delivered', function(bot, message) {

            // get list of mids in this message
            for (var m = 0; m < message.delivery.mids.length; m++) {
                var mid = message.delivery.mids[m];

                // loop through all active conversations this bot is having
                // and mark messages in conversations as delivered = true
                bot.findConversation(message, function(convo) {
                    if (convo) {
                        for (var s = 0; s < convo.sent.length; s++) {
                            if (convo.sent[s].sent_timestamp <= message.delivery.watermark ||
                                (convo.sent[s].api_response && convo.sent[s].api_response.mid == mid)) {
                                convo.sent[s].delivered = true;
                            }
                        }
                    }
                });
            }

        });

    }

    // customize the bot definition, which will be used when new connections
    // spawn!
    facebook_botkit.defineBot(function(botkit, config) {

        var bot = {
            type: 'fb',
            botkit: botkit,
            config: config || {},
            utterances: botkit.utterances,
        };

        bot.startConversation = function(message, cb) {
            botkit.startConversation(this, message, cb);
        };

        bot.createConversation = function(message, cb) {
            botkit.createConversation(this, message, cb);
        };

        bot.send = function(message, cb) {

            var facebook_message = {
                recipient: {},
                message: message.sender_action ? undefined : {}
            };

            if (typeof(message.channel) == 'string' && message.channel.match(/\+\d+\(\d\d\d\)\d\d\d\-\d\d\d\d/)) {
                facebook_message.recipient.phone_number = message.channel;
            } else {
                facebook_message.recipient.id = message.channel;
            }

            if (!message.sender_action) {
                if (message.text) {
                    facebook_message.message.text = message.text;
                }

                if (message.attachment) {
                    facebook_message.message.attachment = message.attachment;
                }

                if (message.sticker_id) {
                    facebook_message.message.sticker_id = message.sticker_id;
                }

                if (message.quick_replies) {

                    // sanitize the length of the title to maximum of 20 chars
                    var titleLimit = function(title) {
                        if (title.length > 20) {
                            var newTitle = title.substring(0, 16) + '...';
                            return newTitle;
                        } else {
                            return title;
                        }
                    };

                    facebook_message.message.quick_replies = message.quick_replies.map(function(item) {
                        return {
                            content_type: item.content_type || 'text',
                            title: titleLimit(item.title),
                            payload: item.payload,
                            image_url: item.image_url,
                        };
                    });
                }
            } else {
                facebook_message.sender_action = message.sender_action;
            }

            if (message.sender_action) {
                facebook_message.sender_action = message.sender_action;
            }

            if (message.notification_type) {
                facebook_message.notification_type = message.notification_type;
            }

            //Add Access Token to outgoing request
            facebook_message.access_token = configuration.access_token;

            request({
                method: 'POST',
                json: true,
                headers: {
                    'content-type': 'application/json',
                },
                body: facebook_message,
                uri: 'https://graph.facebook.com/v2.6/me/messages'
            },
                function(err, res, body) {

                    if (err) {
                        botkit.debug('WEBHOOK ERROR', err);
                        return cb && cb(err);
                    }

                    if (body.error) {
                        botkit.debug('API ERROR', body.error);
                        return cb && cb(body.error.message);
                    }

                    botkit.debug('WEBHOOK SUCCESS', body);
                    cb && cb(null, body);
                });
        };

        bot.startTyping = function(src, cb) {
            var msg = {};
            msg.channel = src.channel;
            msg.sender_action = 'typing_on';
            bot.say(msg, cb);
        };

        bot.stopTyping = function(src, cb) {
            var msg = {};
            msg.channel = src.channel;
            msg.sender_action = 'typing_off';
            bot.say(msg, cb);
        };

        bot.replyWithTyping = function(src, resp, cb) {
            var text;

            if (typeof(resp) == 'string') {
                text = resp;
            } else {
                text = resp.text;
            }

            var avgWPM = 85;
            var avgCPM = avgWPM * 7;

            var typingLength = Math.min(Math.floor(text.length / (avgCPM / 60)) * 1000, 5000);

            bot.startTyping(src, function(err) {
                if (err) console.log(err);
                setTimeout(function() {
                    bot.reply(src, resp, cb);
                }, typingLength);
            });

        };

        bot.reply = function(src, resp, cb) {
            var msg = {};

            if (typeof(resp) == 'string') {
                msg.text = resp;
            } else {
                msg = resp;
            }

            msg.channel = src.channel;

            bot.say(msg, cb);
        };

        bot.findConversation = function(message, cb) {
            botkit.debug('CUSTOM FIND CONVO', message.user, message.channel);
            for (var t = 0; t < botkit.tasks.length; t++) {
                for (var c = 0; c < botkit.tasks[t].convos.length; c++) {
                    if (
                        botkit.tasks[t].convos[c].isActive() &&
                        botkit.tasks[t].convos[c].source_message.user == message.user
                    ) {
                        botkit.debug('FOUND EXISTING CONVO!');
                        cb(botkit.tasks[t].convos[c]);
                        return;
                    }
                }
            }

            cb();
        };

        return bot;
    });

    // set up a web route for receiving outgoing webhooks and/or slash commands

    facebook_botkit.createWebhookEndpoints = function(webserver, bot, cb) {

        facebook_botkit.log(
            '** Serving webhook endpoints for Messenger Platform at: ' +
            'http://' + facebook_botkit.config.hostname + ':' + facebook_botkit.config.port + '/facebook/receive');
        webserver.post('/facebook/receive', function(req, res) {

            facebook_botkit.debug('GOT A MESSAGE HOOK');
            var obj = req.body;
            if (obj.entry) {
                for (var e = 0; e < obj.entry.length; e++) {
                    for (var m = 0; m < obj.entry[e].messaging.length; m++) {
                        var facebook_message = obj.entry[e].messaging[m];
                        if (facebook_message.message) {
                            var message = {
                                text: facebook_message.message.text,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                                seq: facebook_message.message.seq,
                                is_echo: facebook_message.message.is_echo,
                                mid: facebook_message.message.mid,
                                sticker_id: facebook_message.message.sticker_id,
                                attachments: facebook_message.message.attachments,
                                quick_reply: facebook_message.message.quick_reply,
                                type: 'user_message',
                            };

                            facebook_botkit.receiveMessage(bot, message);
                        } else if (facebook_message.postback) {

                            // trigger BOTH a facebook_postback event
                            // and a normal message received event.
                            // this allows developers to receive postbacks as part of a conversation.
                            var message = {
                                text: facebook_message.postback.payload,
                                payload: facebook_message.postback.payload,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                            };

                            facebook_botkit.trigger('facebook_postback', [bot, message]);

                            if (facebook_botkit.config.receive_via_postback) {
                                var message = {
                                    text: facebook_message.postback.payload,
                                    user: facebook_message.sender.id,
                                    channel: facebook_message.sender.id,
                                    timestamp: facebook_message.timestamp,
                                    type: 'facebook_postback',
                                };

                                facebook_botkit.receiveMessage(bot, message);
                            }

                        } else if (facebook_message.optin) {

                            var message = {
                                optin: facebook_message.optin,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                            };

                            facebook_botkit.trigger('facebook_optin', [bot, message]);
                        } else if (facebook_message.delivery) {

                            var message = {
                                delivery: facebook_message.delivery,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                            };

                            facebook_botkit.trigger('message_delivered', [bot, message]);
                        } else if (facebook_message.read) {

                            var message = {
                                read: facebook_message.read,
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                            };

                            facebook_botkit.trigger('message_read', [bot, message]);
                        } else if (facebook_message.referral) {
                            var message = {
                                user: facebook_message.sender.id,
                                channel: facebook_message.sender.id,
                                timestamp: facebook_message.timestamp,
                                referral: facebook_message.referral,
                            };

                            facebook_botkit.trigger('facebook_referral', [bot, message]);
                        }  else {
                            facebook_botkit.log('Got an unexpected message from Facebook: ', facebook_message);
                        }
                    }
                }
            }
            res.send('ok');
        });

        webserver.get('/facebook/receive', function(req, res) {
            if (req.query['hub.mode'] == 'subscribe') {
                if (req.query['hub.verify_token'] == configuration.verify_token) {
                    res.send(req.query['hub.challenge']);
                } else {
                    res.send('OK');
                }
            }
        });

        if (cb) {
            cb();
        }

        return facebook_botkit;
    };

    facebook_botkit.setupWebserver = function(port, cb) {

        if (!port) {
            throw new Error('Cannot start webserver without a port');
        }

        var static_dir =  __dirname + '/public';

        if (facebook_botkit.config && facebook_botkit.config.webserver && facebook_botkit.config.webserver.static_dir)
            static_dir = facebook_botkit.config.webserver.static_dir;

        facebook_botkit.config.port = port;

        facebook_botkit.webserver = express();

        // Validate that requests come from facebook, and abort on validation errors
        if (facebook_botkit.config.validate_requests === true) {
            // Load verify middleware just for post route on our receive webhook, and catch any errors it might throw to prevent the request from being parsed further.
            facebook_botkit.webserver.post('/facebook/receive', bodyParser.json({verify: verifyRequest}))
            facebook_botkit.webserver.use(abortOnValidationError)
        }

        facebook_botkit.webserver.use(bodyParser.json());
        facebook_botkit.webserver.use(bodyParser.urlencoded({ extended: true }));
        facebook_botkit.webserver.use(express.static(static_dir));

        var server = facebook_botkit.webserver.listen(
            facebook_botkit.config.port,
            facebook_botkit.config.hostname,
            function() {
                facebook_botkit.log('** Starting webserver on port ' +
                    facebook_botkit.config.port);
                if (cb) { cb(null, facebook_botkit.webserver); }
            });

        request.post('https://graph.facebook.com/me/subscribed_apps?access_token=' + configuration.access_token,
            function(err, res, body) {
                if (err) {
                    facebook_botkit.log('Could not subscribe to page messages');
                } else {
                    facebook_botkit.debug('Successfully subscribed to Facebook events:', body);
                    facebook_botkit.startTicking();
                }
            });

        return facebook_botkit;

    };

    facebook_botkit.api = {
        'thread_settings': {
            greeting: function(greeting) {
                var message = {
                    'setting_type': 'greeting',
                    'greeting': {
                        'text': greeting
                    }
                };
                facebook_botkit.api.thread_settings.postAPI(message);
            },
            delete_greeting: function() {
                var message = {
                    'setting_type': 'greeting',
                };
                facebook_botkit.api.thread_settings.deleteAPI(message);
            },
            get_started: function(payload) {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'new_thread',
                    'call_to_actions':
                        [
                            {
                                'payload': payload
                            }
                        ]
                };
                facebook_botkit.api.thread_settings.postAPI(message);
            },
            delete_get_started: function() {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'new_thread',
                };
                facebook_botkit.api.thread_settings.deleteAPI(message);
            },
            menu: function(payload) {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'existing_thread',
                    'call_to_actions': payload
                };
                facebook_botkit.api.thread_settings.postAPI(message);
            },
            delete_menu: function() {
                var message = {
                    'setting_type': 'call_to_actions',
                    'thread_state': 'existing_thread',
                };
                facebook_botkit.api.thread_settings.deleteAPI(message);
            },
            postAPI: function(message) {
                request.post('https://graph.facebook.com/v2.6/me/thread_settings?access_token=' + configuration.access_token,
                    {form: message},
                    function(err, res, body) {
                        if (err) {
                            facebook_botkit.log('Could not configure thread settings');
                        } else {

                            var results = null;
                            try {
                                results = JSON.parse(body);
                            } catch (err) {
                                facebook_botkit.log('ERROR in thread_settings API call: Could not parse JSON', err, body);
                            }

                            if (results) {
                                if (results.error) {
                                    facebook_botkit.log('ERROR in thread_settings API call: ', results.error.message);
                                } else {
                                    facebook_botkit.debug('Successfully configured thread settings', body);
                                }
                            }

                        }
                    });
            },
            deleteAPI: function(message) {
                request.delete('https://graph.facebook.com/v2.6/me/thread_settings?access_token=' + configuration.access_token,
                    {form: message},
                    function(err, res, body) {
                        if (err) {
                            facebook_botkit.log('Could not configure thread settings');
                        } else {
                            facebook_botkit.debug('Successfully configured thread settings', message);
                        }
                    });
            }
        }
    };

    // Verifies the SHA1 signature of the raw request payload before bodyParser parses it
    // Will abort parsing if signature is invalid, and pass a generic error to response
    function verifyRequest(req, res, buf, encoding) {
      var expected = req.headers['x-hub-signature'];
      var calculated = getSignature(buf);
      if (expected !== calculated) {
        throw new Error("Invalid signature on incoming request");
      } else {
        facebook_botkit.debug('** X-Hub Verification successful!')
      }
    }

    function getSignature(buf) {
      var hmac = crypto.createHmac("sha1", process.env.fb_app_secret);
      hmac.update(buf, "utf-8");
      return "sha1=" + hmac.digest("hex");
    }

    function abortOnValidationError(err, req, res, next) {
      if (err) {
        facebook_botkit.log('** Invalid X-HUB signature on incoming request!')
        facebook_botkit.debug('** X-HUB Validation Error:', err)
        res.status(400).send({ error: "Invalid signature." });
      } else {
        next();
      }
    }

    return facebook_botkit;
};

module.exports = Facebookbot;
<|endoftext|>


<commit_before>'use strict'

import React, {Component, PropTypes} from "react";
import {View, TouchableWithoutFeedback, Animated} from "react-native";

import S from './Style.js'

export default class FlipCard extends Component {
  constructor (props) {
    super(props)
    this.state = {
      isFlipped: !props.flip, // set reversed boolean for detect other side size
      isFlipping: false,
      rotate: new Animated.Value(Number(props.flip)),
      mesured: false, // the flag to check whether it is measured or not.
      height: 0,
      width: 0,
      face: {width: 0, height: 0},
      back: {width: 0, height: 0}
    }
  }
  componentWillReceiveProps (nextProps) {
    if (this.props.flip !== nextProps.flip) {
      this._toggleCard()
    }
  }
  _toggleCard () {
    this.setState({isFlipping: true})
    this._animation(!this.state.isFlipped)
  }
  _animation (isFlipped) {
    if (!this.timer) {
      this.timer = setTimeout(() => {
        this.setState({isFlipped: !this.state.isFlipped})
        this.timer = null
      }, 120)
    }
    Animated.spring(this.state.rotate,
     {
        toValue: Number(isFlipped),
        friction: this.props.friction
      }
    ).start((param) => {
      this.setState({isFlipping: false})
      this.props.onFlipped(this.state.isFlipped)
    })
  }

  componentDidMount () {

    setTimeout(this.measureOtherSide.bind(this), 8)
  }

  measureOtherSide () {
    this.setState({
      isFlipped: !this.state.isFlipped,
      mesured: true
    })
  }

  render () {
    var c = this.props.children
    var transform = this.props.perspective ? [{ perspective: this.props.perspective }] : []
    var render_side = false

    if (this.props.flipHorizontal) {
      transform.push(
        {rotateY: this.state.rotate.interpolate({
          inputRange: [0, 1],
          outputRange: [ '0deg', '180deg' ]
        })}
      )
    }
    if (this.props.flipVertical) {
      transform.push(
        {rotateX: this.state.rotate.interpolate({
          inputRange: [0, 1],
          outputRange: [ '0deg', '180deg' ]
        })}
      )
    }

    if (this.state.isFlipped) {
      render_side = (
        <Back
          ref='back'
          style={[ this.state.height > 0 && {height: this.state.height}, this.state.width > 0 && {width: this.state.width}]}
          flipHorizontal={this.props.flipHorizontal}
          flipVertical={this.props.flipVertical}
          onLayout={(event) => {
            var {x, y, width, height} = event.nativeEvent.layout
            var _update = Object.assign(this.state.back, {width: width, height: height})
            this.setState({back: _update})
            if (this.state.mesured) {
              if (this.props.alignHeight) {
                this.setState({height: Math.max(this.state.face.height, this.state.back.height)})
              }
              if (this.props.alignWidth) {
                this.setState({width: Math.max(this.state.face.width, this.state.back.width)})
              }
            }
          }}
        >
          {c[1]}
        </Back>
      )
    } else {
      render_side = (
        <Face
          ref='face'
          style={[ this.state.height > 0 && {height: this.state.height}, this.state.width > 0 && {width: this.state.width}]}
          onLayout={(event) => {
            var {x, y, width, height} = event.nativeEvent.layout;
            var _update = Object.assign(this.state.face, {width: width, height: height})
            this.setState({face: _update})
            if (this.state.mesured) {
              if (this.props.alignHeight) {
                this.setState({height: Math.max(this.state.face.height, this.state.back.height)})
              }
              if (this.props.alignWidth) {
                this.setState({width: Math.max(this.state.face.width, this.state.back.width)})
              }
            }
          }}
        >
          {c[0]}
        </Face>
      )
    }

    // FIX: ScrollView inside Flip Card not scrollable
    // TODO: Replace below fix with by using "disabled" function (RN 0.21 exclusive)
    // REF: https://github.com/facebook/react-native/pull/5931
    //      https://github.com/facebook/react-native/issues/2103
    if (this.props.clickable) {
      return (
        <TouchableWithoutFeedback
          onPress={() => {
              this._toggleCard();
          }}
        >
          <Animated.View
            ref='animatedView'
            {...this.props}
            style={[S.flipCard,
            {transform: transform},
            {opacity: +!!this.state.mesured},
            this.props.style
            ]}
          >
            {render_side}
          </Animated.View>
        </TouchableWithoutFeedback>
      )
    } else {
      return (
        <Animated.View
          {...this.props}
          style={[S.flipCard,
          {transform: transform},
          this.props.style
          ]}
        >
          {render_side}
        </Animated.View>
      )
    }
  }
}
FlipCard.propTypes = {
  flip: PropTypes.bool,
  friction: PropTypes.number,
  flipHorizontal: PropTypes.bool,
  flipVertical: PropTypes.bool,
  clickable: PropTypes.bool,
  onFlipped: PropTypes.func,
  alignHeight: PropTypes.bool,
  alignWidth: PropTypes.bool,
  style: View.propTypes.style,
  children (props, propName, componentName) {
    const prop = props[propName]
    if (React.Children.count(prop) !== 2) {
      return new Error(
        '`' + componentName + '` ' +
        'should contain exactly two children. ' +
        'The first child represents the front of the card. ' +
        'The second child represents the back of the card.'
      )
    }
  }
}

FlipCard.defaultProps = {
  flip: false,
  friction: 6,
  flipHorizontal: false,
  flipVertical: true,
  clickable: true,
  onFlipped: () => {}
}

export class Face extends Component {
  render () {
    return (
      <View
        style={[S.face, this.props.style]}
        onLayout={this.props.onLayout}
      >
        {this.props.children}
      </View>
    )
  }
}

Face.propTypes = {
  children (props, propName, componentName) {
  }
}

export class Back extends Component {
  render () {
    var transform = []
    if (this.props.flipHorizontal) {
      transform.push({skewY: '180deg'})
    }
    if (this.props.flipVertical) {
      transform.push({skewX: '180deg'})
    }

    return (
      <View ref='back'style={[
        S.back,
        this.props.style,
        {transform: transform}
        ]}
        onLayout={this.props.onLayout}>
        {this.props.children}
      </View>
    )
  }
}

Back.defaultProps = {
  flipHorizontal: false,
  flipVertical: true
}

Back.propTypes = {
  flipHorizontal: PropTypes.bool,
  flipVertical: PropTypes.bool,
  children (props, propName, componentName) {
  }
}
<commit_msg>Add perspective propType & default value<commit_after>'use strict'

import React, {Component, PropTypes} from "react";
import {View, TouchableWithoutFeedback, Animated} from "react-native";

import S from './Style.js'

export default class FlipCard extends Component {
  constructor (props) {
    super(props)
    this.state = {
      isFlipped: !props.flip, // set reversed boolean for detect other side size
      isFlipping: false,
      rotate: new Animated.Value(Number(props.flip)),
      mesured: false, // the flag to check whether it is measured or not.
      height: 0,
      width: 0,
      face: {width: 0, height: 0},
      back: {width: 0, height: 0}
    }
  }
  componentWillReceiveProps (nextProps) {
    if (this.props.flip !== nextProps.flip) {
      this._toggleCard()
    }
  }
  _toggleCard () {
    this.setState({isFlipping: true})
    this._animation(!this.state.isFlipped)
  }
  _animation (isFlipped) {
    if (!this.timer) {
      this.timer = setTimeout(() => {
        this.setState({isFlipped: !this.state.isFlipped})
        this.timer = null
      }, 120)
    }
    Animated.spring(this.state.rotate,
     {
        toValue: Number(isFlipped),
        friction: this.props.friction
      }
    ).start((param) => {
      this.setState({isFlipping: false})
      this.props.onFlipped(this.state.isFlipped)
    })
  }

  componentDidMount () {

    setTimeout(this.measureOtherSide.bind(this), 8)
  }

  measureOtherSide () {
    this.setState({
      isFlipped: !this.state.isFlipped,
      mesured: true
    })
  }

  render () {
    var c = this.props.children
    var transform = this.props.perspective ? [{ perspective: this.props.perspective }] : []
    var render_side = false

    if (this.props.flipHorizontal) {
      transform.push(
        {rotateY: this.state.rotate.interpolate({
          inputRange: [0, 1],
          outputRange: [ '0deg', '180deg' ]
        })}
      )
    }
    if (this.props.flipVertical) {
      transform.push(
        {rotateX: this.state.rotate.interpolate({
          inputRange: [0, 1],
          outputRange: [ '0deg', '180deg' ]
        })}
      )
    }

    if (this.state.isFlipped) {
      render_side = (
        <Back
          ref='back'
          style={[ this.state.height > 0 && {height: this.state.height}, this.state.width > 0 && {width: this.state.width}]}
          flipHorizontal={this.props.flipHorizontal}
          flipVertical={this.props.flipVertical}
          onLayout={(event) => {
            var {x, y, width, height} = event.nativeEvent.layout
            var _update = Object.assign(this.state.back, {width: width, height: height})
            this.setState({back: _update})
            if (this.state.mesured) {
              if (this.props.alignHeight) {
                this.setState({height: Math.max(this.state.face.height, this.state.back.height)})
              }
              if (this.props.alignWidth) {
                this.setState({width: Math.max(this.state.face.width, this.state.back.width)})
              }
            }
          }}
        >
          {c[1]}
        </Back>
      )
    } else {
      render_side = (
        <Face
          ref='face'
          style={[ this.state.height > 0 && {height: this.state.height}, this.state.width > 0 && {width: this.state.width}]}
          onLayout={(event) => {
            var {x, y, width, height} = event.nativeEvent.layout;
            var _update = Object.assign(this.state.face, {width: width, height: height})
            this.setState({face: _update})
            if (this.state.mesured) {
              if (this.props.alignHeight) {
                this.setState({height: Math.max(this.state.face.height, this.state.back.height)})
              }
              if (this.props.alignWidth) {
                this.setState({width: Math.max(this.state.face.width, this.state.back.width)})
              }
            }
          }}
        >
          {c[0]}
        </Face>
      )
    }

    // FIX: ScrollView inside Flip Card not scrollable
    // TODO: Replace below fix with by using "disabled" function (RN 0.21 exclusive)
    // REF: https://github.com/facebook/react-native/pull/5931
    //      https://github.com/facebook/react-native/issues/2103
    if (this.props.clickable) {
      return (
        <TouchableWithoutFeedback
          onPress={() => {
              this._toggleCard();
          }}
        >
          <Animated.View
            ref='animatedView'
            {...this.props}
            style={[S.flipCard,
            {transform: transform},
            {opacity: +!!this.state.mesured},
            this.props.style
            ]}
          >
            {render_side}
          </Animated.View>
        </TouchableWithoutFeedback>
      )
    } else {
      return (
        <Animated.View
          {...this.props}
          style={[S.flipCard,
          {transform: transform},
          this.props.style
          ]}
        >
          {render_side}
        </Animated.View>
      )
    }
  }
}
FlipCard.propTypes = {
  flip: PropTypes.bool,
  friction: PropTypes.number,
  perspective: PropTypes.number,
  flipHorizontal: PropTypes.bool,
  flipVertical: PropTypes.bool,
  clickable: PropTypes.bool,
  onFlipped: PropTypes.func,
  alignHeight: PropTypes.bool,
  alignWidth: PropTypes.bool,
  style: View.propTypes.style,
  children (props, propName, componentName) {
    const prop = props[propName]
    if (React.Children.count(prop) !== 2) {
      return new Error(
        '`' + componentName + '` ' +
        'should contain exactly two children. ' +
        'The first child represents the front of the card. ' +
        'The second child represents the back of the card.'
      )
    }
  }
}

FlipCard.defaultProps = {
  flip: false,
  friction: 6,
  perspective: 0,
  flipHorizontal: false,
  flipVertical: true,
  clickable: true,
  onFlipped: () => {}
}

export class Face extends Component {
  render () {
    return (
      <View
        style={[S.face, this.props.style]}
        onLayout={this.props.onLayout}
      >
        {this.props.children}
      </View>
    )
  }
}

Face.propTypes = {
  children (props, propName, componentName) {
  }
}

export class Back extends Component {
  render () {
    var transform = []
    if (this.props.flipHorizontal) {
      transform.push({skewY: '180deg'})
    }
    if (this.props.flipVertical) {
      transform.push({skewX: '180deg'})
    }

    return (
      <View ref='back'style={[
        S.back,
        this.props.style,
        {transform: transform}
        ]}
        onLayout={this.props.onLayout}>
        {this.props.children}
      </View>
    )
  }
}

Back.defaultProps = {
  flipHorizontal: false,
  flipVertical: true
}

Back.propTypes = {
  flipHorizontal: PropTypes.bool,
  flipVertical: PropTypes.bool,
  children (props, propName, componentName) {
  }
}
<|endoftext|>