<commit_before>/*
 * Scroller
 * http://github.com/zynga/scroller
 *
 * Copyright 2011, Zynga Inc.
 * Licensed under the MIT License.
 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
 *
 * Based on the work of: Unify Project (unify-project.org)
 * http://unify-project.org
 * Copyright 2011, Deutsche Telekom AG
 * License: MIT + Apache (V2)
 */

/**
 * Generic animation class with support for dropped frames both optional easing and duration.
 *
 * Optional duration is useful when the lifetime is defined by another condition than time
 * e.g. speed of an animating object, etc.
 *
 * Dropped frame logic allows to keep using the same updater logic independent from the actual
 * rendering. This eases a lot of cases where it might be pretty complex to break down a state
 * based on the pure time difference.
 */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        factory(exports);
    } else {
        // Browser globals
        factory((root.Animate = {}));
    }
}(this, function (exports) {
    var time = Date.now || function() {
        return +new Date();
    };
    var desiredFrames = 60;
    var millisecondsPerSecond = 1000;
    var running = {};
    var counter = 1;

    /**
     * A requestAnimationFrame wrapper / polyfill.
     *
     * @param callback {Function} The callback to be invoked before the next repaint.
     * @param root {HTMLElement} The root element for the repaint
     */
    exports.requestAnimationFrame = (function() {
        // Check for request animation Frame support
        var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
        var isNative = !!requestFrame;

        if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
            isNative = false;
        }

        if (isNative) {
            return function(callback, root) {
                requestFrame(callback, root);
            };
        }

        var TARGET_FPS = 60;
        var requests = {};
        var requestCount = 0;
        var rafHandle = 1;
        var intervalHandle = null;
        var lastActive = +new Date();

        return function(callback, root) {
            var callbackHandle = rafHandle++;

            // Store callback
            requests[callbackHandle] = callback;
            requestCount++;

            // Create timeout at first request
            if (intervalHandle === null) {

                intervalHandle = setInterval(function() {

                    var time = +new Date();
                    var currentRequests = requests;

                    // Reset data structure before executing callbacks
                    requests = {};
                    requestCount = 0;

                    for(var key in currentRequests) {
                        if (currentRequests.hasOwnProperty(key)) {
                            currentRequests[key](time);
                            lastActive = time;
                        }
                    }

                    // Disable the timeout when nothing happens for a certain
                    // period of time
                    if (time - lastActive > 2500) {
                        clearInterval(intervalHandle);
                        intervalHandle = null;
                    }

                }, 1000 / TARGET_FPS);
            }

            return callbackHandle;
        };

    })();

    /**
     * Stops the given animation.
     *
     * @param id {Integer} Unique animation ID
     * @return {Boolean} Whether the animation was stopped (aka, was running before)
     */
    exports.stop = function(id) {
        var cleared = (running[id] !== null);
        if (cleared) {
            running[id] = null;
        }

        return cleared;
    };

    /**
     * Whether the given animation is still running.
     *
     * @param id {Integer} Unique animation ID
     * @return {Boolean} Whether the animation is still running
     */
    exports.isRunning = function(id) {
        return running[id] !== null;
    };

    /**
     * Start the animation.
     *
     * @param stepCallback {Function} Pointer to function which is executed on every step.
     *   Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
     * @param verifyCallback {Function} Executed before every animation step.
     *   Signature of the method should be `function() { return continueWithAnimation; }`
     * @param completedCallback {Function}
     *   Signature of the method should be `function(droppedFrames, finishedAnimation, optional wasFinished) {}`
     * @param duration {Integer} Milliseconds to run the animation
     * @param easingMethod {Function} Pointer to easing function
     *   Signature of the method should be `function(percent) { return modifiedValue; }`
     * @param root {Element ? document.body} Render root, when available. Used for internal
     *   usage of requestAnimationFrame.
     * @return {Integer} Identifier of animation. Can be used to stop it any time.
     */
    exports.start = function(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
        var start = time();
        var lastFrame = start;
        var percent = 0;
        var dropCounter = 0;
        var id = counter++;

        if (!root) {
            root = document.body;
        }

        // Compacting running db automatically every few new animations
        if (id % 20 === 0) {
            var newRunning = {};
            for (var usedId in running) {
                newRunning[usedId] = true;
            }
            running = newRunning;
        }

        // This is the internal step method which is called every few milliseconds
        var step = function(virtual) {

            // Normalize virtual value
            var render = virtual !== true;

            // Get current time
            var now = time();

            // Verification is executed before next animation step
            if (!running[id] || (verifyCallback && !verifyCallback(id))) {

                running[id] = null;
                completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
                return;

            }

            // For the current rendering to apply let's update omitted steps in memory.
            // This is important to bring internal state variables up-to-date with progress in time.
            if (render) {

                var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
                for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
                    step(true);
                    dropCounter++;
                }

            }

            // Compute percent value
            if (duration) {
                percent = (now - start) / duration;
                if (percent > 1) {
                    percent = 1;
                }
            }

            // Execute step callback, then...
            var value = easingMethod ? easingMethod(percent) : percent;
            if ((stepCallback(value, now, render) === false || percent === 1) && render) {
                running[id] = null;
                completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration === undefined);
            } else if (render) {
                lastFrame = now;
                exports.requestAnimationFrame(step, root);
            }
        };

        // Mark as running
        running[id] = true;

        // Init first step
        Animate.requestAnimationFrame(step, root);

        // Return unique animation ID
        return id;
    };
}));
<commit_msg>Style obsessing<commit_after>/*
 * Scroller
 * http://github.com/zynga/scroller
 *
 * Copyright 2011, Zynga Inc.
 * Licensed under the MIT License.
 * https://raw.github.com/zynga/scroller/master/MIT-LICENSE.txt
 *
 * Based on the work of: Unify Project (unify-project.org)
 * http://unify-project.org
 * Copyright 2011, Deutsche Telekom AG
 * License: MIT + Apache (V2)
 */

/**
 * Generic animation class with support for dropped frames both optional easing and duration.
 *
 * Optional duration is useful when the lifetime is defined by another condition than time
 * e.g. speed of an animating object, etc.
 *
 * Dropped frame logic allows to keep using the same updater logic independent from the actual
 * rendering. This eases a lot of cases where it might be pretty complex to break down a state
 * based on the pure time difference.
 */
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['exports'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        factory(exports);
    } else {
        // Browser globals
        factory((root.Animate = {}));
    }
}(this, function (exports) {
    var time = Date.now || function () {
        return +new Date();
    };
    var desiredFrames = 60;
    var millisecondsPerSecond = 1000;
    var running = {};
    var counter = 1;

    /**
     * A requestAnimationFrame wrapper / polyfill.
     *
     * @param callback {Function} The callback to be invoked before the next repaint.
     * @param root {HTMLElement} The root element for the repaint
     */
    exports.requestAnimationFrame = (function () {
        // Check for request animation Frame support
        var requestFrame = global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame;
        var isNative = !!requestFrame;

        if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) {
            isNative = false;
        }

        if (isNative) {
            return function (callback, root) {
                requestFrame(callback, root);
            };
        }

        var TARGET_FPS = 60;
        var requests = {};
        var requestCount = 0;
        var rafHandle = 1;
        var intervalHandle = null;
        var lastActive = +new Date();

        return function (callback, root) {
            var callbackHandle = rafHandle++;

            // Store callback
            requests[callbackHandle] = callback;
            requestCount++;

            // Create timeout at first request
            if (intervalHandle === null) {

                intervalHandle = setInterval(function () {

                    var time = +new Date();
                    var currentRequests = requests;

                    // Reset data structure before executing callbacks
                    requests = {};
                    requestCount = 0;

                    for(var key in currentRequests) {
                        if (currentRequests.hasOwnProperty(key)) {
                            currentRequests[key](time);
                            lastActive = time;
                        }
                    }

                    // Disable the timeout when nothing happens for a certain
                    // period of time
                    if (time - lastActive > 2500) {
                        clearInterval(intervalHandle);
                        intervalHandle = null;
                    }

                }, 1000 / TARGET_FPS);
            }

            return callbackHandle;
        };

    })();

    /**
     * Stops the given animation.
     *
     * @param id {Integer} Unique animation ID
     * @return {Boolean} Whether the animation was stopped (aka, was running before)
     */
    exports.stop = function (id) {
        var cleared = (running[id] !== null);
        if (cleared) {
            running[id] = null;
        }

        return cleared;
    };

    /**
     * Whether the given animation is still running.
     *
     * @param id {Integer} Unique animation ID
     * @return {Boolean} Whether the animation is still running
     */
    exports.isRunning = function (id) {
        return running[id] !== null;
    };

    /**
     * Start the animation.
     *
     * @param stepCallback {Function} Pointer to function which is executed on every step.
     *   Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }`
     * @param verifyCallback {Function} Executed before every animation step.
     *   Signature of the method should be `function() { return continueWithAnimation; }`
     * @param completedCallback {Function}
     *   Signature of the method should be `function(droppedFrames, finishedAnimation, optional wasFinished) {}`
     * @param duration {Integer} Milliseconds to run the animation
     * @param easingMethod {Function} Pointer to easing function
     *   Signature of the method should be `function(percent) { return modifiedValue; }`
     * @param root {Element ? document.body} Render root, when available. Used for internal
     *   usage of requestAnimationFrame.
     * @return {Integer} Identifier of animation. Can be used to stop it any time.
     */
    exports.start = function (stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) {
        var start = time();
        var lastFrame = start;
        var percent = 0;
        var dropCounter = 0;
        var id = counter++;

        if (!root) {
            root = document.body;
        }

        // Compacting running db automatically every few new animations
        if (id % 20 === 0) {
            var newRunning = {};
            for (var usedId in running) {
                newRunning[usedId] = true;
            }
            running = newRunning;
        }

        // This is the internal step method which is called every few milliseconds
        var step = function (virtual) {

            // Normalize virtual value
            var render = virtual !== true;

            // Get current time
            var now = time();

            // Verification is executed before next animation step
            if (!running[id] || (verifyCallback && !verifyCallback(id))) {

                running[id] = null;
                completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, false);
                return;

            }

            // For the current rendering to apply let's update omitted steps in memory.
            // This is important to bring internal state variables up-to-date with progress in time.
            if (render) {

                var droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1;
                for (var j = 0; j < Math.min(droppedFrames, 4); j++) {
                    step(true);
                    dropCounter++;
                }

            }

            // Compute percent value
            if (duration) {
                percent = (now - start) / duration;
                if (percent > 1) {
                    percent = 1;
                }
            }

            // Execute step callback, then...
            var value = easingMethod ? easingMethod(percent) : percent;
            if ((stepCallback(value, now, render) === false || percent === 1) && render) {
                running[id] = null;
                completedCallback(desiredFrames - (dropCounter / ((now - start) / millisecondsPerSecond)), id, percent === 1 || duration === undefined);
            } else if (render) {
                lastFrame = now;
                exports.requestAnimationFrame(step, root);
            }
        };

        // Mark as running
        running[id] = true;

        // Init first step
        Animate.requestAnimationFrame(step, root);

        // Return unique animation ID
        return id;
    };
}));
<|endoftext|>


<commit_before>/*
 * Helma License Notice
 *
 * The contents of this file are subject to the Helma License
 * Version 2.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://adele.helma.org/download/helma/license.txt
 *
 * Copyright 1998-2006 Helma Software. All Rights Reserved.
 *
 * $RCSfile: Object.js,v $
 * $Author: zumbrunn $
 * $Revision: 8714 $
 * $Date: 2007-12-13 13:21:48 +0100 (Don, 13 Dez 2007) $
 */

// importModule('helma.logging', 'logging');
// var log = logging.getLog(__name__);

__shared__ = true;

/**
 * @fileoverview Adds useful methods to the JavaScript Object type.
 *

 * To use this optional module, its repository needs to be added to the
 * application, for example by calling app.addRepository('modules/core/Object.js')
 */

/**
 * Set the DONTENUM attribute on one or more properties on this object.
 * @param One or more property names or index numbers
 */
Object.prototype.dontEnum = function() {
    var rhino = new JavaImporter(org.mozilla.javascript);
    var length = arguments.length;
    var cx = rhino.Context.currentContext;
    var wrapped = cx.wrapFactory.wrapAsJavaObject(cx, global, this, null);
    for (var i = 0; i < length; i++) {
        if (!this.hasOwnProperty(arguments[i])) {
            continue;
        }
        try {
            wrapped.setAttributes(arguments[i], rhino.ScriptableObject.DONTENUM);
        } catch (e) {
            java.lang.System.err.println("Error in dontEnum: " + e);
        }
    }
    return;
}

/**
 * copy the properties of an object into
 * a new object
 * @param Object the source object
 * @param Object the (optional) target object
 * @return Object the resulting object
 */
Object.prototype.clone = function(clone, recursive) {
   if (!clone)
      clone = new this.constructor();
   var value;
   for (var propName in this) {
      value = this[propName];
      if (recursive && (value.constructor == HopObject || value.constructor == Object)) {
         clone[propName] = value.clone(new value.constructor(), recursive);
      } else {
         clone[propName] = value;
      }
   }
   return clone;
};

/**
 * reduce an extended object (ie. a HopObject)
 * to a generic javascript object
 * @param HopObject the HopObject to be reduced
 * @return Object the resulting generic object
 */
Object.prototype.reduce = function(recursive) {
    var result = {};
    for (var i in this) {
        if (this[i] instanceof HopObject == false)
            result[i] = this[i];
        else if (recursive)
            result[i] = this.reduce(true);
    }
    return result;
};

/**
 * print the contents of an object for debugging
 * @param Object the object to dump
 * @param Boolean recursive flag (if true, dump child objects, too)
 */
Object.prototype.dump = function(recursive) {
    var beginList = "
";
    var endList = "
";
    var beginItem = "
";
    var endItem = "
";
    var beginKey = "";
    var endKey = ": ";
    res.write(beginList);
    for (var p in this) {
        res.write(beginItem);
        res.write(beginKey);
        res.write(p);
        res.write(endKey);
        if (recursive && typeof this[p] == "object") {
            var recurse = true;
            var types = [Function, Date, String, Number];
            for (var i in types) {
                if (this[p] instanceof types[i]) {
                    recurse = false
                    break;
                }
            }
            if (recurse == true)
                this[p].dump(true);
            else {
                res.write(this[p].toSource());
            }
        } else if (this[p]) {
            res.write(encode(this[p].toSource()));
        }
        res.write(endItem);
    }
    res.write(endList);
    return;
};

// prevent any newly added properties from being enumerated
for (var i in Object.prototype)
   Object.prototype.dontEnum(i);
<commit_msg>Implement Object.prototype.readOnly() to set properties to READONLY<commit_after>/*
 * Helma License Notice
 *
 * The contents of this file are subject to the Helma License
 * Version 2.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://adele.helma.org/download/helma/license.txt
 *
 * Copyright 1998-2006 Helma Software. All Rights Reserved.
 *
 * $RCSfile: Object.js,v $
 * $Author: zumbrunn $
 * $Revision: 8714 $
 * $Date: 2007-12-13 13:21:48 +0100 (Don, 13 Dez 2007) $
 */

// importModule('helma.logging', 'logging');
// var log = logging.getLog(__name__);

__shared__ = true;

/**
 * @fileoverview Adds useful methods to the JavaScript Object type.
 *

 * To use this optional module, its repository needs to be added to the
 * application, for example by calling app.addRepository('modules/core/Object.js')
 */

/**
 * Set the DONTENUM attribute on one or more properties on this object.
 * @param One or more property names or index numbers
 */
Object.prototype.dontEnum = function() {
    var rhino = new JavaImporter(org.mozilla.javascript);
    var length = arguments.length;
    var cx = rhino.Context.currentContext;
    var wrapped = cx.wrapFactory.wrapAsJavaObject(cx, global, this, null);
    for (var i = 0; i < length; i++) {
        if (!this.hasOwnProperty(arguments[i])) {
            continue;
        }
        try {
            wrapped.setAttributes(arguments[i], rhino.ScriptableObject.DONTENUM);
        } catch (e) {
            java.lang.System.err.println("Error in dontEnum: " + e);
        }
    }
    return;
}

/**
 * Set the READONLY attribute on one or more properties on this object.
 * @param One or more property names or index numbers
 */
Object.prototype.readOnly = function() {
    var rhino = new JavaImporter(org.mozilla.javascript);
    var length = arguments.length;
    var cx = rhino.Context.currentContext;
    var wrapped = cx.wrapFactory.wrapAsJavaObject(cx, global, this, null);
    for (var i = 0; i < length; i++) {
        if (!this.hasOwnProperty(arguments[i])) {
            continue;
        }
        try {
            wrapped.setAttributes(arguments[i], rhino.ScriptableObject.READONLY);
        } catch (e) {
            java.lang.System.err.println("Error in readOnly: " + e);
        }
    }
    return;
}

/**
 * copy the properties of an object into
 * a new object
 * @param Object the source object
 * @param Object the (optional) target object
 * @return Object the resulting object
 */
Object.prototype.clone = function(clone, recursive) {
   if (!clone)
      clone = new this.constructor();
   var value;
   for (var propName in this) {
      value = this[propName];
      if (recursive && (value.constructor == HopObject || value.constructor == Object)) {
         clone[propName] = value.clone(new value.constructor(), recursive);
      } else {
         clone[propName] = value;
      }
   }
   return clone;
};

/**
 * reduce an extended object (ie. a HopObject)
 * to a generic javascript object
 * @param HopObject the HopObject to be reduced
 * @return Object the resulting generic object
 */
Object.prototype.reduce = function(recursive) {
    var result = {};
    for (var i in this) {
        if (this[i] instanceof HopObject == false)
            result[i] = this[i];
        else if (recursive)
            result[i] = this.reduce(true);
    }
    return result;
};

/**
 * print the contents of an object for debugging
 * @param Object the object to dump
 * @param Boolean recursive flag (if true, dump child objects, too)
 */
Object.prototype.dump = function(recursive) {
    var beginList = "
";
    var endList = "
";
    var beginItem = "
";
    var endItem = "
";
    var beginKey = "";
    var endKey = ": ";
    res.write(beginList);
    for (var p in this) {
        res.write(beginItem);
        res.write(beginKey);
        res.write(p);
        res.write(endKey);
        if (recursive && typeof this[p] == "object") {
            var recurse = true;
            var types = [Function, Date, String, Number];
            for (var i in types) {
                if (this[p] instanceof types[i]) {
                    recurse = false
                    break;
                }
            }
            if (recurse == true)
                this[p].dump(true);
            else {
                res.write(this[p].toSource());
            }
        } else if (this[p]) {
            res.write(encode(this[p].toSource()));
        }
        res.write(endItem);
    }
    res.write(endList);
    return;
};

// prevent any newly added properties from being enumerated
for (var i in Object.prototype)
   Object.prototype.dontEnum(i);
<|endoftext|>


<commit_before>"use strict";

const ch = require('./changes_hunter');
const glob = require('glob');
const fs = require('fs-extra');
const _ = require('lodash');
const lh = require('./lineage_hunter');
const Pattern = require('./object_factory').Pattern;
const path = require('path');
const logger = require('./log');
const lineage_hunter = new lh();
const changes_hunter = new ch();

const pseudopattern_hunter = function () {};

pseudopattern_hunter.prototype.find_pseudopatterns = function (currentPattern, patternlab) {
  const pa = require('./pattern_assembler');
  const pattern_assembler = new pa();

  const paths = patternlab.config.paths;

  //look for a pseudo pattern by checking if there is a file containing same
  //name, with ~ in it, ending in .json
  const needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json';
  const pseudoPatterns = glob.sync(needle, {
    cwd: paths.source.patterns,
    debug: false,
    nodir: true
  });

  if (pseudoPatterns.length > 0) {
    for (let i = 0; i < pseudoPatterns.length; i++) {
      logger.debug(`Found pseudoPattern variant of ${currentPattern.patternPartial}`);

      //we want to do everything we normally would here, except instead read the pseudoPattern data
      try {
        var variantFileFullPath = path.resolve(paths.source.patterns, pseudoPatterns[i]);
        var variantFileData = fs.readJSONSync(variantFileFullPath);
      } catch (err) {
        logger.warning(`There was an error parsing pseudopattern JSON for ${currentPattern.relPath}`);
        logger.warning(err);
      }

      //extend any existing data with variant data
      variantFileData = _.merge(currentPattern.jsonFileData, variantFileData);

      let variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0];
      let variantFilePath = path.join(currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json');
      let lm = fs.statSync(variantFileFullPath);
      let patternVariant = Pattern.create(variantFilePath, variantFileData, {
        //use the same template as the non-variant
        template: currentPattern.template,
        fileExtension: currentPattern.fileExtension,
        extendedTemplate: currentPattern.extendedTemplate,
        isPseudoPattern: true,
        basePattern: currentPattern,
        stylePartials: currentPattern.stylePartials,
        parameteredPartials: currentPattern.parameteredPartials,

        // Only regular patterns are discovered during iterative walks
        // Need to recompile on data change or template change
        lastModified: Math.max(currentPattern.lastModified, lm.mtime),

        // use the same template engine as the non-variant
        engine: currentPattern.engine
      }, patternlab);

      changes_hunter.checkBuildState(patternVariant, patternlab);
      patternlab.graph.add(patternVariant);
      patternlab.graph.link(patternVariant, currentPattern);

      //process the companion markdown file if it exists
      pattern_assembler.parse_pattern_markdown(patternVariant, patternlab);

      //find pattern lineage
      lineage_hunter.find_lineage(patternVariant, patternlab);

      //add to patternlab object so we can look these up later.
      pattern_assembler.addPattern(patternVariant, patternlab);

      //we want to do everything we normally would here, except instead read the pseudoPattern data
      try {
        var variantFileFullPath = path.resolve(paths.source.patterns, pseudoPatterns[i]);
        var variantFileData = fs.readJSONSync(variantFileFullPath);
      } catch (err) {
        logger.warning(`There was an error parsing pseudopattern JSON for ${currentPattern.relPath}`);
        logger.warning(err);
      }

      //extend any existing data with variant data
      variantFileData = _.merge(currentPattern.jsonFileData, variantFileData);

      variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0];
      variantFilePath = path.join(currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json');
      lm = fs.statSync(variantFileFullPath);
      patternVariant = Pattern.create(variantFilePath, variantFileData, {
        //use the same template as the non-variant
        template: currentPattern.template,
        fileExtension: currentPattern.fileExtension,
        extendedTemplate: currentPattern.extendedTemplate,
        isPseudoPattern: true,
        basePattern: currentPattern,
        stylePartials: currentPattern.stylePartials,
        parameteredPartials: currentPattern.parameteredPartials,

        // Only regular patterns are discovered during iterative walks
        // Need to recompile on data change or template change
        lastModified: Math.max(currentPattern.lastModified, lm.mtime),

        // use the same template engine as the non-variant
        engine: currentPattern.engine
      }, patternlab);

      changes_hunter.checkBuildState(patternVariant, patternlab);
      patternlab.graph.add(patternVariant);
      patternlab.graph.link(patternVariant, currentPattern);

      //process the companion markdown file if it exists
      pattern_assembler.parse_pattern_markdown(patternVariant, patternlab);

      //find pattern lineage
      lineage_hunter.find_lineage(patternVariant, patternlab);

      //add to patternlab object so we can look these up later.
      pattern_assembler.addPattern(patternVariant, patternlab);
    }

    // GTP: this is to emulate the behavior of the stale asynced
    // version; when we have time, we can make all the FS calls in here
    // async and see if it helps any, but it didn't when I tried it.
  }
  return Promise.resolve();
};

module.exports = new pseudopattern_hunter();
<commit_msg>chore(pseudopattern_hunter): Clean up duplicate code and lint errors<commit_after>"use strict";

const ch = require('./changes_hunter');
const glob = require('glob');
const fs = require('fs-extra');
const _ = require('lodash');
const lh = require('./lineage_hunter');
const Pattern = require('./object_factory').Pattern;
const path = require('path');
const logger = require('./log');
const lineage_hunter = new lh();
const changes_hunter = new ch();

const pseudopattern_hunter = function () {};

pseudopattern_hunter.prototype.find_pseudopatterns = function (currentPattern, patternlab) {
  const pa = require('./pattern_assembler');
  const pattern_assembler = new pa();

  const paths = patternlab.config.paths;

  //look for a pseudo pattern by checking if there is a file containing same
  //name, with ~ in it, ending in .json
  const needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json';
  const pseudoPatterns = glob.sync(needle, {
    cwd: paths.source.patterns,
    debug: false,
    nodir: true
  });

  if (pseudoPatterns.length > 0) {
    for (let i = 0; i < pseudoPatterns.length; i++) {
      logger.debug(`Found pseudoPattern variant of ${currentPattern.patternPartial}`);

      //we want to do everything we normally would here, except instead read the pseudoPattern data
      try {
        var variantFileFullPath = path.resolve(paths.source.patterns, pseudoPatterns[i]);
        var variantFileData = fs.readJSONSync(variantFileFullPath);
      } catch (err) {
        logger.warning(`There was an error parsing pseudopattern JSON for ${currentPattern.relPath}`);
        logger.warning(err);
      }

      //extend any existing data with variant data
      variantFileData = _.merge(currentPattern.jsonFileData, variantFileData);

      const variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0];
      const variantFilePath = path.join(currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json');
      const lm = fs.statSync(variantFileFullPath);
      const patternVariant = Pattern.create(variantFilePath, variantFileData, {
        //use the same template as the non-variant
        template: currentPattern.template,
        fileExtension: currentPattern.fileExtension,
        extendedTemplate: currentPattern.extendedTemplate,
        isPseudoPattern: true,
        basePattern: currentPattern,
        stylePartials: currentPattern.stylePartials,
        parameteredPartials: currentPattern.parameteredPartials,

        // Only regular patterns are discovered during iterative walks
        // Need to recompile on data change or template change
        lastModified: Math.max(currentPattern.lastModified, lm.mtime),

        // use the same template engine as the non-variant
        engine: currentPattern.engine
      }, patternlab);

      changes_hunter.checkBuildState(patternVariant, patternlab);
      patternlab.graph.add(patternVariant);
      patternlab.graph.link(patternVariant, currentPattern);

      //process the companion markdown file if it exists
      pattern_assembler.parse_pattern_markdown(patternVariant, patternlab);

      //find pattern lineage
      lineage_hunter.find_lineage(patternVariant, patternlab);

      //add to patternlab object so we can look these up later.
      pattern_assembler.addPattern(patternVariant, patternlab);
    }

    // GTP: this is to emulate the behavior of the stale asynced
    // version; when we have time, we can make all the FS calls in here
    // async and see if it helps any, but it didn't when I tried it.
  }
  return Promise.resolve();
};

module.exports = new pseudopattern_hunter();
<|endoftext|>


<commit_before>/**
 * Created by njporter10 on 9/27/16.
 */
function webSocketClose() {
    ws.close();
}
function webSocketInit() {
    console.log('testing');
    if ("WebSocket" in window) {

        console.log('connecting');
        // Let us open a web socket
        ws = new WebSocket("wss://www.mnoster.github.io/chatroom/");

        ws.onopen = function () {
            // Web Socket is connected, send data using send()
            socket_connected = true;
            display_connection();
            console.log('connected');
            send_name();

            //$("#connect").text('connected').prop('disabled',true);
        };

        ws.onmessage = function (evt) {
            var received_msg = evt.data;
            console.log('message is received', evt);
            var packet = JSON.parse(evt.data);
            var html = null;
            var refresh = false;
            if (packet.type == 'history') {
                html = create_all_messages(packet.data);
                refresh = true;
            }
            else if (packet.type == 'message') {
                html = create_message(packet.data);
            }
            else {
                console.log('unknown type');
                return
            }
            update_message_display(html, refresh);

        };

        ws.onclose = function () {
            // websocket is closed.
            socket_connected = false;
            display_connection();
            alert("Connection is closed...");
        };
    }

    else {
        // The browser doesn't support WebSocket
        alert("WebSocket NOT supported by your Browser!");
    }
}
//-----------------------------------------------

var socket_connected = false;
var ws = null;
function display_connection() {
    if (!socket_connected) {
        $("#connect").text('connect');
    } else {
        $("#connect").text('disconnect');
    }
}
function socket_toggle() {
    if (socket_connected === false) {
        webSocketInit();
    } else {
        webSocketClose();
    }
}
function send_name() {
    console.log('sent name config');

    var packet = {
        action: 'config',
        name: $("#username").val()
    };
    message_transmit(packet);
}
function message_transmit(obj) {
    if (ws) {
        ws.send(JSON.stringify(obj));
    }
    else {
        console.log('no connection to send');
    }
}
function send_message() {

    var packet = {
        action: 'message',
        message: $("#outbound_message").val()
    };
    message_transmit(packet);

}
function create_message(message) {
    var separator = $('
');
    var message_row = $("
", {
        class: 'message_row',
        append: separator
    });
    var message_text = $("
", {
        class: 'message_text',
        text: message.text
    });
    var message_sender = $("
", {
        class: 'message_sender',
        text: message.author + ':'
    });
    message_row.append(message_sender, message_text);
    return message_row;
}
function create_all_messages(history) {
    console.log(history);
    var message_container = $("
");
    for (var i = 0; i < history.length; i++) {
        message_container.append(create_message(history[i]));
    }
    return message_container;
}
var user_color = function() {
    var min = Math.ceil(1);
    var max = Math.floor(7);
    var color_index =  Math.floor(Math.random() * (max - min)) + min;
    // console.log("index:" ,color_index);
    var colors = ["red","yellow","green","00FFFF","orange","#ff24bf","white"];
    var color = colors[color_index];

    return color;
    // $(".message_sender").css("color", color);
};
function update_message_display(html, refresh) {
    if (refresh) {
        $("#message_area").html('');

    }
    $("#message_area").append(html);

        var sender_name = $(".message_sender").text();
        var sender_array = sender_name.split(":");
        console.log("sender array: ", sender_array);
        var temp = null;
        for (var i = 0; i < sender_array.length -1; i++) {
            temp = sender_array[i];
            $(".message_sender:contains(" + temp + ':' + ")").css("color", user_color());
            console.log(user_color());
        }
}

$(document).ready(function () {
    $("#connect").click(function () {
        socket_toggle();
    });
    $("#send").click(function () {
        var message = $('#outbound_message').val();
        if(message == '' || message == ' '){
           return;
        }
        console.log('send button clicked');
        send_message();
        $('#outbound_message').val('');
    });
    $("#outbound_message").on('keypress', function (e) {
        if (e.which == 13) {
            console.log('enter key pressed');
            send_message();
        }
    });

});
<commit_msg>changed port<commit_after>/**
 * Created by njporter10 on 9/27/16.
 */
function webSocketClose() {
    ws.close();
}
function webSocketInit() {
    console.log('testing');
    if ("WebSocket" in window) {

        console.log('connecting');
        // Let us open a web socket
        ws = new WebSocket("wss://https://www.mnoster.github.io/chatroom/");

        ws.onopen = function () {
            // Web Socket is connected, send data using send()
            socket_connected = true;
            display_connection();
            console.log('connected');
            send_name();

            //$("#connect").text('connected').prop('disabled',true);
        };

        ws.onmessage = function (evt) {
            var received_msg = evt.data;
            console.log('message is received', evt);
            var packet = JSON.parse(evt.data);
            var html = null;
            var refresh = false;
            if (packet.type == 'history') {
                html = create_all_messages(packet.data);
                refresh = true;
            }
            else if (packet.type == 'message') {
                html = create_message(packet.data);
            }
            else {
                console.log('unknown type');
                return
            }
            update_message_display(html, refresh);

        };

        ws.onclose = function () {
            // websocket is closed.
            socket_connected = false;
            display_connection();
            alert("Connection is closed...");
        };
    }

    else {
        // The browser doesn't support WebSocket
        alert("WebSocket NOT supported by your Browser!");
    }
}
//-----------------------------------------------

var socket_connected = false;
var ws = null;
function display_connection() {
    if (!socket_connected) {
        $("#connect").text('connect');
    } else {
        $("#connect").text('disconnect');
    }
}
function socket_toggle() {
    if (socket_connected === false) {
        webSocketInit();
    } else {
        webSocketClose();
    }
}
function send_name() {
    console.log('sent name config');

    var packet = {
        action: 'config',
        name: $("#username").val()
    };
    message_transmit(packet);
}
function message_transmit(obj) {
    if (ws) {
        ws.send(JSON.stringify(obj));
    }
    else {
        console.log('no connection to send');
    }
}
function send_message() {

    var packet = {
        action: 'message',
        message: $("#outbound_message").val()
    };
    message_transmit(packet);

}
function create_message(message) {
    var separator = $('
');
    var message_row = $("
", {
        class: 'message_row',
        append: separator
    });
    var message_text = $("
", {
        class: 'message_text',
        text: message.text
    });
    var message_sender = $("
", {
        class: 'message_sender',
        text: message.author + ':'
    });
    message_row.append(message_sender, message_text);
    return message_row;
}
function create_all_messages(history) {
    console.log(history);
    var message_container = $("
");
    for (var i = 0; i < history.length; i++) {
        message_container.append(create_message(history[i]));
    }
    return message_container;
}
var user_color = function() {
    var min = Math.ceil(1);
    var max = Math.floor(7);
    var color_index =  Math.floor(Math.random() * (max - min)) + min;
    // console.log("index:" ,color_index);
    var colors = ["red","yellow","green","00FFFF","orange","#ff24bf","white"];
    var color = colors[color_index];

    return color;
    // $(".message_sender").css("color", color);
};
function update_message_display(html, refresh) {
    if (refresh) {
        $("#message_area").html('');

    }
    $("#message_area").append(html);

        var sender_name = $(".message_sender").text();
        var sender_array = sender_name.split(":");
        console.log("sender array: ", sender_array);
        var temp = null;
        for (var i = 0; i < sender_array.length -1; i++) {
            temp = sender_array[i];
            $(".message_sender:contains(" + temp + ':' + ")").css("color", user_color());
            console.log(user_color());
        }
}

$(document).ready(function () {
    $("#connect").click(function () {
        socket_toggle();
    });
    $("#send").click(function () {
        var message = $('#outbound_message').val();
        if(message == '' || message == ' '){
           return;
        }
        console.log('send button clicked');
        send_message();
        $('#outbound_message').val('');
    });
    $("#outbound_message").on('keypress', function (e) {
        if (e.which == 13) {
            console.log('enter key pressed');
            send_message();
        }
    });

});
<|endoftext|>


<commit_before>/* OpenID for node.js
 *
 * http://ox.no/software/node-openid
 * http://github.com/havard/node-openid
 *
 * Copyright (C) 2010 by Håvard Stranden
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 */
require.paths.unshift(__dirname + '/../');

var assert = require('assert');
var openid = require('openid');

exports.testResolveFailed = function(test)
{
  openid.authenticate('example.com', 'http://example.com/verify', null, false, false,
    function(url, provider)
    {
      assert.equal(null, url);
      assert.equal(null, provider);
      test.done();
    });
}

exports.testEmptyUrl = function(test)
{
  openid.discover('',
    function(data)
    {
      assert.equal(null, data);
      test.done();
    });
}

exports.testResolveRyanXri = function(test)
{
  openid.discover('=ryan',
    function(data)
    {
      assert.equal(2, data.length);
      test.done();
    });
}

exports.testResolveRedirect = function(test)
{
  openid.discover('http://www.myopenid.com/xrds?username=swatinem.myopenid.com',
    function(data)
    {
      assert.equal(3, data.length);
      test.done();
    });
}

exports.testResolveGoogle = function(test)
{
  openid.discover('http://www.google.com/accounts/o8/id',
    function(data)
    {
      assert.equal(1, data.length);
      test.done();
    });
}

exports.testResolveLiveJournalUser = function(test)
{
  openid.discover('http://omnifarious.livejournal.com/',
    function(data)
    {
      assert.equal(1, data.length);
      test.done();
    });
}

exports.testResolveOpenID11 = function(test)
{
  openid.discover('http://www.michaelwales.com/',
    function(data)
    {
      assert.equal(1, data.length);
      test.done();
    });
}

function associateTest(url, test)
{
  openid.discover(url,
    function(providers)
    {
      var provider = providers[0];
      openid.associate(provider, function(data)
      {
        assert.ok(data.expires_in);
        test.done();
      });
    }
  );
}

exports.testAssociateWithGoogle = function(test)
{
  associateTest('http://www.google.com/accounts/o8/id', test);
}

exports.testAssociateWithLiveJournal = function(test)
{
  associateTest('http://omnifarious.livejournal.com/', test);
}

exports.testAssociateWithOpenID11 = function(test)
{
  associateTest('http://www.michaelwales.com/', test);
}

exports.testImmediateAuthenticationWithGoogle = function(test)
{
  openid.authenticate('http://www.google.com/accounts/o8/id',
  'http://example.com/verify', null, true, false, function(url)
  {
    assert.ok(url.indexOf('checkid_immediate') !== -1);
    test.done();
  });
}

exports.testImmediateAuthenticationWithGoogleAppsForDomains = function(test)
{
  // domain must be a valid google apps domain with openid enabled.
  openid.authenticate('https://www.google.com/accounts/o8/site-xrds?hd=opower.com',
  'http://example.com/verify', null, true, false, function(url)
  {
    assert.ok(url.indexOf('checkid_immediate') !== -1);
    test.done();
  });
}

exports.testSetupAuthenticationWithGoogle = function(test)
{
  openid.authenticate('http://www.google.com/accounts/o8/id',
  'http://example.com/verify', null, false, false, function(url)
  {
    assert.ok(url.indexOf('checkid_setup') !== -1);
    test.done();
  });
}

exports.testAuthenticationWithGoogleUsingRelyingPartyObject = function(test)
{
  var rp = new openid.RelyingParty(
      'http://example.com/verify',
      null,
      false,
      false,
      null);
  rp.authenticate('http://www.google.com/accounts/o8/id', false, function(url)
  {
    assert.ok(url.indexOf('checkid_setup') !== -1);
    test.done();
  });
}
<commit_msg>Use Blogger for OpenID 1.1 tests<commit_after>/* OpenID for node.js
 *
 * http://ox.no/software/node-openid
 * http://github.com/havard/node-openid
 *
 * Copyright (C) 2010 by Håvard Stranden
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 */
require.paths.unshift(__dirname + '/../');

var assert = require('assert');
var openid = require('openid');

exports.testResolveFailed = function(test)
{
  openid.authenticate('example.com', 'http://example.com/verify', null, false, false,
    function(url, provider)
    {
      assert.equal(null, url);
      assert.equal(null, provider);
      test.done();
    });
}

exports.testEmptyUrl = function(test)
{
  openid.discover('',
    function(data)
    {
      assert.equal(null, data);
      test.done();
    });
}

exports.testResolveRyanXri = function(test)
{
  openid.discover('=ryan',
    function(data)
    {
      assert.equal(2, data.length);
      test.done();
    });
}

exports.testResolveRedirect = function(test)
{
  openid.discover('http://www.myopenid.com/xrds?username=swatinem.myopenid.com',
    function(data)
    {
      assert.equal(3, data.length);
      test.done();
    });
}

exports.testResolveGoogle = function(test)
{
  openid.discover('http://www.google.com/accounts/o8/id',
    function(data)
    {
      assert.equal(1, data.length);
      test.done();
    });
}

exports.testResolveLiveJournalUser = function(test)
{
  openid.discover('http://omnifarious.livejournal.com/',
    function(data)
    {
      assert.equal(1, data.length);
      test.done();
    });
}

exports.testResolveOpenID11 = function(test)
{
  openid.discover('http://buzz.blogger.com/',
    function(data)
    {
      assert.equal(1, data.length);
      test.done();
    });
}

function associateTest(url, test)
{
  openid.discover(url,
    function(providers)
    {
      var provider = providers[0];
      openid.associate(provider, function(data)
      {
        assert.ok(data.expires_in);
        test.done();
      });
    }
  );
}

exports.testAssociateWithGoogle = function(test)
{
  associateTest('http://www.google.com/accounts/o8/id', test);
}

exports.testAssociateWithLiveJournal = function(test)
{
  associateTest('http://omnifarious.livejournal.com/', test);
}

exports.testAssociateWithOpenID11 = function(test)
{
  associateTest('http://buzz.blogger.com/', test);
}

exports.testImmediateAuthenticationWithGoogle = function(test)
{
  openid.authenticate('http://www.google.com/accounts/o8/id',
  'http://example.com/verify', null, true, false, function(url)
  {
    assert.ok(url.indexOf('checkid_immediate') !== -1);
    test.done();
  });
}

exports.testImmediateAuthenticationWithGoogleAppsForDomains = function(test)
{
  // domain must be a valid google apps domain with openid enabled.
  openid.authenticate('https://www.google.com/accounts/o8/site-xrds?hd=opower.com',
  'http://example.com/verify', null, true, false, function(url)
  {
    assert.ok(url.indexOf('checkid_immediate') !== -1);
    test.done();
  });
}

exports.testSetupAuthenticationWithGoogle = function(test)
{
  openid.authenticate('http://www.google.com/accounts/o8/id',
  'http://example.com/verify', null, false, false, function(url)
  {
    assert.ok(url.indexOf('checkid_setup') !== -1);
    test.done();
  });
}

exports.testAuthenticationWithGoogleUsingRelyingPartyObject = function(test)
{
  var rp = new openid.RelyingParty(
      'http://example.com/verify',
      null,
      false,
      false,
      null);
  rp.authenticate('http://www.google.com/accounts/o8/id', false, function(url)
  {
    assert.ok(url.indexOf('checkid_setup') !== -1);
    test.done();
  });
}
<|endoftext|>


<commit_before>"use strict";

/* global Promise */

var forEachAction = require( "./forEachAction" );
var later = require( "./later" );
var oneShotCallbacks = require( "./oneShotCallbacks" );
var repeatCallbacks =  require( "./repeatCallbacks" );

function Attempt( init ) {
	var successCallbacks = oneShotCallbacks();
	var failureCallbacks = oneShotCallbacks();
	var progressCallbacks = repeatCallbacks();
	var self = this;
	var then;
	var abort;
	var tmp;
	forEachAction( [
		successCallbacks,
		failureCallbacks,
		progressCallbacks
	], function( handler, methodName ) {
		self[ methodName ] = handler.a;
	} );
	function successFire() {
		abort = false;
		failureCallbacks.l();
		progressCallbacks.l();
		successCallbacks.f.apply( null, arguments );
	}
	function failureFire() {
		abort = false;
		successCallbacks.l();
		progressCallbacks.l();
		failureCallbacks.f.apply( null, arguments );
	}
	if ( typeof ( then = init.then ) === "function" ) {
		then.call( init, successFire, failureFire );
	} else {
		tmp = init.call( this, successFire, failureFire, progressCallbacks.f );
		if ( tmp ) {
			self.abort = function() {
				if ( !abort ) {
					return false;
				}
				if ( typeof abort === "function" ) {
					later( abort );
				}
				successCallbacks.l();
				failureCallbacks.l();
				progressCallbacks.l();
				abort = false;
				return true;
			};
			if ( abort !== false ) {
				abort = tmp;
			}
		}
	}
}

Attempt.prototype = {
	always: function( callback ) {
		return this.success( callback ).failure( callback );
	},
	chain: function() {
		var self = this;
		var callbacks = arguments;
		return new Attempt( function() {
			var notifiers = arguments;
			forEachAction( callbacks, function( callback, methodName, index ) {
				self[ methodName ]( callback ? function() {
					var returned = callback.apply( null, arguments );
					var then;
					if ( returned instanceof Attempt ) {
						forEachAction( notifiers, function( notifier, methodName ) {
							returned[ methodName ]( notifier );
						} );
					} else if ( typeof ( then = returned.then ) === "function" ) {
						then.call( returned, notifiers[ 0 ], notifiers[ 1 ] );
					} else {
						notifiers[ index ]( returned );
					}
				} : notifiers[ index ] );
			} );
		} );
	},
	promise: function() {
		var self = this;
		return new Promise( function() {
			forEachAction( arguments, function( notifier, methodName ) {
				if ( notifier ) {
					self[ methodName ]( function( arg ) {
						notifier( arguments.length > 1 ? [].slice.call( arguments ) : arg );
					} );
				}
			} );
		} );
	}
};

forEachAction( function( methodName, _, index ) {
	if ( index < 2 ) {
		Attempt[ methodName ] = function() {
			var args = arguments;
			return new Attempt( function() {
				arguments[ index ].apply( null, args );
			} );
		};
	}
} );

module.exports = Attempt;
<commit_msg>Scope the use of global Promise<commit_after>"use strict";

var forEachAction = require( "./forEachAction" );
var later = require( "./later" );
var oneShotCallbacks = require( "./oneShotCallbacks" );
var repeatCallbacks =  require( "./repeatCallbacks" );

function Attempt( init ) {
	var successCallbacks = oneShotCallbacks();
	var failureCallbacks = oneShotCallbacks();
	var progressCallbacks = repeatCallbacks();
	var self = this;
	var then;
	var abort;
	var tmp;
	forEachAction( [
		successCallbacks,
		failureCallbacks,
		progressCallbacks
	], function( handler, methodName ) {
		self[ methodName ] = handler.a;
	} );
	function successFire() {
		abort = false;
		failureCallbacks.l();
		progressCallbacks.l();
		successCallbacks.f.apply( null, arguments );
	}
	function failureFire() {
		abort = false;
		successCallbacks.l();
		progressCallbacks.l();
		failureCallbacks.f.apply( null, arguments );
	}
	if ( typeof ( then = init.then ) === "function" ) {
		then.call( init, successFire, failureFire );
	} else {
		tmp = init.call( this, successFire, failureFire, progressCallbacks.f );
		if ( tmp ) {
			self.abort = function() {
				if ( !abort ) {
					return false;
				}
				if ( typeof abort === "function" ) {
					later( abort );
				}
				successCallbacks.l();
				failureCallbacks.l();
				progressCallbacks.l();
				abort = false;
				return true;
			};
			if ( abort !== false ) {
				abort = tmp;
			}
		}
	}
}

Attempt.prototype = {
	always: function( callback ) {
		return this.success( callback ).failure( callback );
	},
	chain: function() {
		var self = this;
		var callbacks = arguments;
		return new Attempt( function() {
			var notifiers = arguments;
			forEachAction( callbacks, function( callback, methodName, index ) {
				self[ methodName ]( callback ? function() {
					var returned = callback.apply( null, arguments );
					var then;
					if ( returned instanceof Attempt ) {
						forEachAction( notifiers, function( notifier, methodName ) {
							returned[ methodName ]( notifier );
						} );
					} else if ( typeof ( then = returned.then ) === "function" ) {
						then.call( returned, notifiers[ 0 ], notifiers[ 1 ] );
					} else {
						notifiers[ index ]( returned );
					}
				} : notifiers[ index ] );
			} );
		} );
	},
	promise: function() {
		/* global Promise */
		var self = this;
		return new Promise( function() {
			forEachAction( arguments, function( notifier, methodName ) {
				if ( notifier ) {
					self[ methodName ]( function( arg ) {
						notifier( arguments.length > 1 ? [].slice.call( arguments ) : arg );
					} );
				}
			} );
		} );
	}
};

forEachAction( function( methodName, _, index ) {
	if ( index < 2 ) {
		Attempt[ methodName ] = function() {
			var args = arguments;
			return new Attempt( function() {
				arguments[ index ].apply( null, args );
			} );
		};
	}
} );

module.exports = Attempt;
<|endoftext|>


<commit_before>$(function () {

    $.when(NRDB.user.deferred).then(function () {
        if(NRDB.user.data.is_authenticated) {
            if(NRDB.user.data.following.indexOf(UserId) > -1) {
                $('#unfollow').show();
            } else {
                $('#follow').show();
            }
        }
    });

    $('#unfollow').mouseover(function (event) {
        $(this).addClass('btn-danger').removeClass('btn-info').html(' Unfollow');
    }).mouseout(function (event) {
        $(this).addClass('btn-info').removeClass('btn-danger').html(' Following');
    });

    $('a.btn').click(function (event) {
        event.preventDefault();
        var href = $(this).attr('href');
        NRDB.user.wipe();
        setTimeout(function () {
            location.href = href;
        }, 100);
    });

});<commit_msg>fix follow/unfollow button<commit_after>$(function () {

    $.when(NRDB.user.deferred).then(function () {
        if(NRDB.user.data.is_authenticated) {
            if(NRDB.user.data.following.indexOf(UserId) > -1) {
                $('#unfollow').show();
            } else {
                $('#follow').show();
            }
        }
    });

    $('#unfollow').mouseover(function (event) {
        $(this).addClass('btn-danger').removeClass('btn-info').html(' Unfollow');
    }).mouseout(function (event) {
        $(this).addClass('btn-info').removeClass('btn-danger').html(' Following');
    });

    $('a.btn').click(function (event) {
        event.preventDefault();
        var href = $(this).attr('href');
        setTimeout(function () {
            location.href = href;
        }, 100);
    });

});
<|endoftext|>


<commit_before>// @flow

/* globals Request, URL */

import {cachedFetch} from './cached'
import {userAgent} from '@frogpond/constants'
import delay from 'delay'
import queryString from 'query-string'

export {insertForUrl} from './cached'

const USER_AGENT = userAgent()

class HTTPError extends Error {
	response: Response
	constructor(response) {
		super(response.statusText)
		this.name = 'HTTPError'
		this.response = response
	}
}

type ExpandedFetchArgs = RequestOptions & {
	// Search parameters to include in the request URL. Setting this will
	// override all existing search parameters in the input URL.
	searchParams?: {[key: string]: string | number},

	// Number of times to retry failed requests.
	// retry?: number;

	// Timeout in milliseconds for getting a response.
	// timeout?: number;

	// Throw a `HTTPError` for error responses (non-2xx status codes).
	throwHttpErrors?: boolean,

	// Amount of time to delay the request, in milliseconds.
	delay?: number,
}

interface ResponsePromise extends Promise<Response> {
	json(): Promise<any>;
	text(): Promise<string>;
}

class Fetch {
	request: Request
	response: Response

	retryCount: number = 0

	options: ExpandedFetchArgs

	startMs: number

	constructor(input: RequestInfo = '', init?: ExpandedFetchArgs = {}) {
		let {searchParams = null} = init

		this.options = {
			throwHttpErrors: true,
			delay: 0,
			...init,
		}

		this.request = new Request(input, init)

		if (searchParams) {
			let url = this.request.url.split('?')[0]
			// $FlowExpectedError
			let queryParams = queryString.stringify(searchParams)
			this.request.url = `${url}?${queryParams}`
		}

		if (!this.request.headers.has('User-Agent')) {
			this.request.headers.set('User-Agent', USER_AGENT)
		}

		this.startMs = Date.now()

		this.response = this.fetch()

		// $FlowExpectedError: we're purposefully attaching these properties to a promise
		this.response.text = async () => {
			return (await this.response).clone().text()
		}

		// $FlowExpectedError: we're purposefully attaching these properties to a promise
		this.response.json = async () => {
			return (await this.response).clone().json()
		}

		return this.response
	}

	async fetch() {
		let response = await cachedFetch(this.request)

		if (this.options.throwHttpErrors && !response.ok) {
			throw new HTTPError(response)
		}

		let elapsed = Date.now() - this.startMs
		if (this.options.delay && elapsed < this.options.delay) {
			// 0.5s delay for ListViews – if we let them go at full speed, it feels broken
			await delay(this.options.delay - elapsed)
		}

		return response
	}
}

export const fetch = (
	input: RequestInfo = '',
	init?: RequestOptions & ExpandedFetchArgs = {},
): ResponsePromise => (new Fetch(input, init): any)
<commit_msg>remove unused URL global from frogpond/fetch/index<commit_after>// @flow

/* globals Request */

import {cachedFetch} from './cached'
import {userAgent} from '@frogpond/constants'
import delay from 'delay'
import queryString from 'query-string'

export {insertForUrl} from './cached'

const USER_AGENT = userAgent()

class HTTPError extends Error {
	response: Response
	constructor(response) {
		super(response.statusText)
		this.name = 'HTTPError'
		this.response = response
	}
}

type ExpandedFetchArgs = RequestOptions & {
	// Search parameters to include in the request URL. Setting this will
	// override all existing search parameters in the input URL.
	searchParams?: {[key: string]: string | number},

	// Number of times to retry failed requests.
	// retry?: number;

	// Timeout in milliseconds for getting a response.
	// timeout?: number;

	// Throw a `HTTPError` for error responses (non-2xx status codes).
	throwHttpErrors?: boolean,

	// Amount of time to delay the request, in milliseconds.
	delay?: number,
}

interface ResponsePromise extends Promise<Response> {
	json(): Promise<any>;
	text(): Promise<string>;
}

class Fetch {
	request: Request
	response: Response

	retryCount: number = 0

	options: ExpandedFetchArgs

	startMs: number

	constructor(input: RequestInfo = '', init?: ExpandedFetchArgs = {}) {
		let {searchParams = null} = init

		this.options = {
			throwHttpErrors: true,
			delay: 0,
			...init,
		}

		this.request = new Request(input, init)

		if (searchParams) {
			let url = this.request.url.split('?')[0]
			// $FlowExpectedError
			let queryParams = queryString.stringify(searchParams)
			this.request.url = `${url}?${queryParams}`
		}

		if (!this.request.headers.has('User-Agent')) {
			this.request.headers.set('User-Agent', USER_AGENT)
		}

		this.startMs = Date.now()

		this.response = this.fetch()

		// $FlowExpectedError: we're purposefully attaching these properties to a promise
		this.response.text = async () => {
			return (await this.response).clone().text()
		}

		// $FlowExpectedError: we're purposefully attaching these properties to a promise
		this.response.json = async () => {
			return (await this.response).clone().json()
		}

		return this.response
	}

	async fetch() {
		let response = await cachedFetch(this.request)

		if (this.options.throwHttpErrors && !response.ok) {
			throw new HTTPError(response)
		}

		let elapsed = Date.now() - this.startMs
		if (this.options.delay && elapsed < this.options.delay) {
			// 0.5s delay for ListViews – if we let them go at full speed, it feels broken
			await delay(this.options.delay - elapsed)
		}

		return response
	}
}

export const fetch = (
	input: RequestInfo = '',
	init?: RequestOptions & ExpandedFetchArgs = {},
): ResponsePromise => (new Fetch(input, init): any)
<|endoftext|>


<commit_before>'use strict';
const Chat = require('./Chat');
const Conversation = require('./Conversation');
const EventEmitter = require('eventemitter3');
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const fetch = require('node-fetch');
const normalizeString = require('./utils/normalize-string');

class BootBot extends EventEmitter {
  constructor(options) {
    super();
    if (!options || (options && (!options.accessToken || !options.verifyToken || !options.appSecret))) {
      throw new Error('You need to specify an accessToken, verifyToken and appSecret');
    }
    this.accessToken = options.accessToken;
    this.verifyToken = options.verifyToken;
    this.appSecret = options.appSecret;
    this.broadcastEchoes = options.broadcastEchoes || false;
    this.app = express();
    this.app.use(bodyParser.json({ verify: this._verifyRequestSignature.bind(this) }));
    this._hearMap = [];
    this._conversations = [];
  }

  start(port) {
    this._initWebhook();
    this.app.set('port', port || 3000);
    this.server = this.app.listen(this.app.get('port'), () => {
      const portNum = this.app.get('port');
      console.log('BootBot running on port', portNum);
      console.log(`Facebook Webhook running on localhost:${portNum}/webhook`);
    });
  }

  close() {
    this.server.close();
  }

  sendTextMessage(recipientId, text, quickReplies, options) {
    const message = { text };
    const formattedQuickReplies = this._formatQuickReplies(quickReplies);
    if (formattedQuickReplies && formattedQuickReplies.length > 0) {
      message.quick_replies = formattedQuickReplies;
    }
    return this.sendMessage(recipientId, message, options);
  }

  sendButtonTemplate(recipientId, text, buttons, options) {
    const payload = {
      template_type: 'button',
      text
    };
    const formattedButtons = this._formatButtons(buttons);
    payload.buttons = formattedButtons;
    return this.sendTemplate(recipientId, payload, options);
  }

  sendGenericTemplate(recipientId, elements, options) {
    const payload = {
      template_type: 'generic',
      elements
    };
    return this.sendTemplate(recipientId, payload, options);
  }

  sendTemplate(recipientId, payload, options) {
    const message = {
      attachment: {
        type: 'template',
        payload
      }
    };
    return this.sendMessage(recipientId, message, options);
  }

  sendAttachment(recipientId, type, url, quickReplies, options) {
    const message = {
      attachment: {
        type,
        payload: { url }
      }
    };
    const formattedQuickReplies = this._formatQuickReplies(quickReplies);
    if (formattedQuickReplies && formattedQuickReplies.length > 0) {
      message.quick_replies = formattedQuickReplies;
    }
    return this.sendMessage(recipientId, message, options);
  }

  sendAction(recipientId, action, options) {
    return this.sendRequest({
      recipient: {
        id: recipientId
      },
      sender_action: action
    });
  }

  sendMessage(recipientId, message, options) {
    const onDelivery = options && options.onDelivery;
    const onRead = options && options.onRead;
    const req = () => (
      this.sendRequest({
        recipient: {
          id: recipientId
        },
        message
      }).then((json) => {
        if (typeof onDelivery === 'function') {
          this.once('delivery', onDelivery);
        }
        if (typeof onRead === 'function') {
          this.once('read', onRead);
        }
        return json;
      })
    );
    if (options && options.typing) {
      const autoTimeout = (message && message.text) ? message.text.length * 10 : 1000;
      const timeout = (typeof options.typing === 'number') ? options.typing : autoTimeout;
      return this.sendTypingIndicator(recipientId, timeout).then(req);
    }
    return req();
  }

  sendRequest(body, endpoint, method) {
    endpoint = endpoint || 'messages';
    method = method || 'POST';
    return fetch(`https://graph.facebook.com/v2.6/me/${endpoint}?access_token=${this.accessToken}`, {
      method,
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    })
    .then(res => res.json())
    .then(res => {
      if (res.error) {
        console.log('Messenger Error received. For more information about error codes, see: https://goo.gl/d76uvB');
        console.log(res.error);
      }
      return res;
    })
    .catch(err => console.log(`Error sending message: ${err}`));
  }

  sendThreadRequest(body, method) {
    return this.sendRequest(body, 'thread_settings', method);
  }

  sendTypingIndicator(recipientId, milliseconds) {
    const timeout = isNaN(milliseconds) ? 0 : milliseconds;
    if (milliseconds > 20000) {
      milliseconds = 20000;
      console.error('sendTypingIndicator: max milliseconds value is 20000 (20 seconds)');
    }
    return new Promise((resolve, reject) => {
      return this.sendAction(recipientId, 'typing_on').then(() => {
        setTimeout(() => this.sendAction(recipientId, 'typing_off').then((json) => resolve(json)), timeout);
      });
    });
  }

  getUserProfile(userId) {
    const url = `https://graph.facebook.com/v2.6/${userId}?fields=first_name,last_name,profile_pic,locale,timezone,gender&access_token=${this.accessToken}`;
    return fetch(url)
      .then(res => res.json())
      .catch(err => console.log(`Error getting user profile: ${err}`));
  }

  setGreetingText(text) {
    return this.sendThreadRequest({
      setting_type: 'greeting',
      greeting: { text }
    });
  }

  setGetStartedButton(action) {
    const payload = (typeof action === 'string') ? action : 'BOOTBOT_GET_STARTED';
    if (typeof action === 'function') {
      this.on(`postback:${payload}`, action);
    }
    return this.sendThreadRequest({
      setting_type: 'call_to_actions',
      thread_state: 'new_thread',
      call_to_actions: [ { payload } ]
    });
  }

  deleteGetStartedButton() {
    return this.sendThreadRequest({
      setting_type: 'call_to_actions',
      thread_state: 'new_thread'
    }, 'DELETE');
  }

  setPersistentMenu(buttons) {
    const formattedButtons = this._formatButtons(buttons);
    return this.sendThreadRequest({
      setting_type: 'call_to_actions',
      thread_state: 'existing_thread',
      call_to_actions: formattedButtons
    });
  }

  deletePersistentMenu() {
    return this.sendThreadRequest({
      setting_type: 'call_to_actions',
      thread_state: 'existing_thread'
    }, 'DELETE');
  }

  say(recipientId, message, options) {
    if (typeof message === 'string') {
      return this.sendTextMessage(recipientId, message, [], options);
    } else if (message && message.text) {
      if (message.quickReplies && message.quickReplies.length > 0) {
        return this.sendTextMessage(recipientId, message.text, message.quickReplies, options);
      } else if (message.buttons && message.buttons.length > 0) {
        return this.sendButtonTemplate(recipientId, message.text, message.buttons, options);
      }
    } else if (message && message.attachment) {
      return this.sendAttachment(recipientId, message.attachment, message.url, message.quickReplies, options);
    }
    console.error('Invalid format for .say() message.');
  }

  hear(keywords, callback) {
    keywords = Array.isArray(keywords) ? keywords : [ keywords ];
    keywords.forEach(keyword => this._hearMap.push({ keyword, callback }));
    return this;
  }

  module(factory) {
    return factory.apply(this, [ this ]);
  }

  conversation(recipientId, factory) {
    if (!recipientId || !factory || typeof factory !== 'function') {
      return console.error(`You need to specify a recipient and a callback to start a conversation`);
    }
    const convo = new Conversation(this, recipientId);
    this._conversations.push(convo);
    convo.on('end', (endedConvo) => {
      const removeIndex = this._conversations.indexOf(endedConvo);
      this._conversations.splice(removeIndex, 1);
    });
    factory.apply(this, [ convo ]);
    return convo;
  }

  _formatButtons(buttons) {
    return buttons && buttons.map((button) => {
      if (typeof button === 'string') {
        return {
          type: 'postback',
          title: button,
          payload: 'BOOTBOT_BUTTON_' + normalizeString(button)
        };
      } else if (button && button.title) {
        return button;
      }
      return {};
    });
  }

  _formatQuickReplies(quickReplies) {
    return quickReplies && quickReplies.map((reply) => {
      if (typeof reply === 'string') {
        return {
          content_type: 'text',
          title: reply,
          payload: 'BOOTBOT_QR_' + normalizeString(reply)
        };
      } else if (reply && reply.title) {
        return Object.assign({
          content_type: 'text',
          payload:'BOOTBOT_QR_' + normalizeString(reply.title)
        }, reply);
      }
      return reply;
    });
  }

  _handleEvent(type, event, data) {
    const chat = new Chat(this, event.sender.id);
    this.emit(type, event, chat, data);
  }

  _handleMessageEvent(event) {
    if (this._handleConversationResponse('message', event)) { return; }
    const text = event.message.text;
    const senderId = event.sender.id;
    let captured = false;
    if (!text) { return; }

    this._hearMap.forEach(hear => {
      if (typeof hear.keyword === 'string' && hear.keyword.toLowerCase() === text.toLowerCase()) {
        const res = hear.callback.apply(this, [event, new Chat(this, senderId), {
          keyword: hear.keyword,
          captured
        }]);
        captured = true;
        return res;
      } else if (hear.keyword instanceof RegExp && hear.keyword.test(text)) {
        const res = hear.callback.apply(this, [event, new Chat(this, senderId), {
          keyword: hear.keyword,
          match: text.match(hear.keyword),
          captured
        }]);
        captured = true;
        return res;
      }
    });

    this._handleEvent('message', event, { captured });
  }

  _handleAttachmentEvent(event) {
    if (this._handleConversationResponse('attachment', event)) { return; }
    this._handleEvent('attachment', event);
  }

  _handlePostbackEvent(event) {
    if (this._handleConversationResponse('postback', event)) { return; }
    const payload = event.postback.payload;
    if (payload) {
      this._handleEvent(`postback:${payload}`, event);
    }
    this._handleEvent('postback', event);
  }

  _handleQuickReplyEvent(event) {
    if (this._handleConversationResponse('quick_reply', event)) { return; }
    const payload = event.message.quick_reply && event.message.quick_reply.payload;
    if (payload) {
      this._handleEvent(`quick_reply:${payload}`, event);
    }
    this._handleEvent('quick_reply', event);
  }

  _handleConversationResponse(type, event) {
    const userId = event.sender.id;
    let captured = false;
    this._conversations.forEach(convo => {
      if (userId && userId === convo.userId && convo.isActive()) {
        captured = true;
        return convo.respond(event, { type });
      }
    });
    return captured;
  }

  _initWebhook() {
    this.app.get('/webhook', (req, res) => {
      if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === this.verifyToken) {
        console.log('Validation Succeded.')
        res.status(200).send(req.query['hub.challenge']);
      } else {
        console.error('Failed validation. Make sure the validation tokens match.');
        res.sendStatus(403);
      }
    });

    this.app.post('/webhook', (req, res) => {
      var data = req.body;
      if (data.object !== 'page') {
        return;
      }

      // Iterate over each entry. There may be multiple if batched.
      data.entry.forEach((entry) => {
          // Iterate over each messaging event
          entry.messaging.forEach((event) => {
            if (event.message && event.message.is_echo && !this.broadcastEchoes) {
              return;
            }
            if (event.optin) {
              this._handleEvent('authentication', event);
            } else if (event.message && event.message.text) {
              this._handleMessageEvent(event);
              if (event.message.quick_reply) {
                this._handleQuickReplyEvent(event);
              }
            } else if (event.message && event.message.attachments) {
              this._handleAttachmentEvent(event);
            } else if (event.postback) {
              this._handlePostbackEvent(event);
            } else if (event.delivery) {
              this._handleEvent('delivery', event);
            } else if (event.read) {
              this._handleEvent('read', event);
            } else if (event.account_linking) {
              this._handleEvent('account_linking', event);
            } else {
              console.log('Webhook received unknown event: ', event);
            }
          });
        });

        // Must send back a 200 within 20 seconds or the request will time out.
        res.sendStatus(200);
    });
  }

  _verifyRequestSignature(req, res, buf) {
    var signature = req.headers['x-hub-signature'];
    if (!signature) {
      throw new Error('Couldn\'t validate the request signature.');
    } else {
      var elements = signature.split('=');
      var method = elements[0];
      var signatureHash = elements[1];
      var expectedHash = crypto.createHmac('sha1', this.appSecret)
                          .update(buf)
                          .digest('hex');

      if (signatureHash != expectedHash) {
        throw new Error("Couldn't validate the request signature.");
      }
    }
  }
}

module.exports = BootBot;
<commit_msg>Adds support for 1.4 messenger profile functionality.<commit_after>'use strict';
const Chat = require('./Chat');
const Conversation = require('./Conversation');
const EventEmitter = require('eventemitter3');
const express = require('express');
const bodyParser = require('body-parser');
const crypto = require('crypto');
const fetch = require('node-fetch');
const normalizeString = require('./utils/normalize-string');

class BootBot extends EventEmitter {
  constructor(options) {
    super();
    if (!options || (options && (!options.accessToken || !options.verifyToken || !options.appSecret))) {
      throw new Error('You need to specify an accessToken, verifyToken and appSecret');
    }
    this.accessToken = options.accessToken;
    this.verifyToken = options.verifyToken;
    this.appSecret = options.appSecret;
    this.broadcastEchoes = options.broadcastEchoes || false;
    this.app = express();
    this.app.use(bodyParser.json({ verify: this._verifyRequestSignature.bind(this) }));
    this._hearMap = [];
    this._conversations = [];
  }

  start(port) {
    this._initWebhook();
    this.app.set('port', port || 3000);
    this.server = this.app.listen(this.app.get('port'), () => {
      const portNum = this.app.get('port');
      console.log('BootBot running on port', portNum);
      console.log(`Facebook Webhook running on localhost:${portNum}/webhook`);
    });
  }

  close() {
    this.server.close();
  }

  sendTextMessage(recipientId, text, quickReplies, options) {
    const message = { text };
    const formattedQuickReplies = this._formatQuickReplies(quickReplies);
    if (formattedQuickReplies && formattedQuickReplies.length > 0) {
      message.quick_replies = formattedQuickReplies;
    }
    return this.sendMessage(recipientId, message, options);
  }

  sendButtonTemplate(recipientId, text, buttons, options) {
    const payload = {
      template_type: 'button',
      text
    };
    const formattedButtons = this._formatButtons(buttons);
    payload.buttons = formattedButtons;
    return this.sendTemplate(recipientId, payload, options);
  }

  sendGenericTemplate(recipientId, elements, options) {
    const payload = {
      template_type: 'generic',
      elements
    };
    return this.sendTemplate(recipientId, payload, options);
  }

  sendTemplate(recipientId, payload, options) {
    const message = {
      attachment: {
        type: 'template',
        payload
      }
    };
    return this.sendMessage(recipientId, message, options);
  }

  sendAttachment(recipientId, type, url, quickReplies, options) {
    const message = {
      attachment: {
        type,
        payload: { url }
      }
    };
    const formattedQuickReplies = this._formatQuickReplies(quickReplies);
    if (formattedQuickReplies && formattedQuickReplies.length > 0) {
      message.quick_replies = formattedQuickReplies;
    }
    return this.sendMessage(recipientId, message, options);
  }

  sendAction(recipientId, action, options) {
    return this.sendRequest({
      recipient: {
        id: recipientId
      },
      sender_action: action
    });
  }

  sendMessage(recipientId, message, options) {
    const onDelivery = options && options.onDelivery;
    const onRead = options && options.onRead;
    const req = () => (
      this.sendRequest({
        recipient: {
          id: recipientId
        },
        message
      }).then((json) => {
        if (typeof onDelivery === 'function') {
          this.once('delivery', onDelivery);
        }
        if (typeof onRead === 'function') {
          this.once('read', onRead);
        }
        return json;
      })
    );
    if (options && options.typing) {
      const autoTimeout = (message && message.text) ? message.text.length * 10 : 1000;
      const timeout = (typeof options.typing === 'number') ? options.typing : autoTimeout;
      return this.sendTypingIndicator(recipientId, timeout).then(req);
    }
    return req();
  }

  sendRequest(body, endpoint, method) {
    endpoint = endpoint || 'messages';
    method = method || 'POST';
    return fetch(`https://graph.facebook.com/v2.6/me/${endpoint}?access_token=${this.accessToken}`, {
      method,
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(body)
    })
    .then(res => res.json())
    .then(res => {
      if (res.error) {
        console.log('Messenger Error received. For more information about error codes, see: https://goo.gl/d76uvB');
        console.log(res.error);
      }
      return res;
    })
    .catch(err => console.log(`Error sending message: ${err}`));
  }

  sendProfileRequest(body, method) {
    return this.sendRequest(body, 'messenger_profile', method);
  }

  sendTypingIndicator(recipientId, milliseconds) {
    const timeout = isNaN(milliseconds) ? 0 : milliseconds;
    if (milliseconds > 20000) {
      milliseconds = 20000;
      console.error('sendTypingIndicator: max milliseconds value is 20000 (20 seconds)');
    }
    return new Promise((resolve, reject) => {
      return this.sendAction(recipientId, 'typing_on').then(() => {
        setTimeout(() => this.sendAction(recipientId, 'typing_off').then((json) => resolve(json)), timeout);
      });
    });
  }

  getUserProfile(userId) {
    const url = `https://graph.facebook.com/v2.6/${userId}?fields=first_name,last_name,profile_pic,locale,timezone,gender&access_token=${this.accessToken}`;
    return fetch(url)
      .then(res => res.json())
      .catch(err => console.log(`Error getting user profile: ${err}`));
  }

  setGreetingText(defaultText, localizedTexts) {
    return this.sendProfileRequest({
      greeting: [{
          locale: 'default',
          text: defaultText
        }].concat(localizedTexts)
    });
  }

  setGetStartedButton(action) {
    const payload = (typeof action === 'string') ? action : 'BOOTBOT_GET_STARTED';
    if (typeof action === 'function') {
      this.on(`postback:${payload}`, action);
    }
    return this.sendProfileRequest({
      get_started: {
        payload: payload
      }
    });
  }

  deleteGetStartedButton() {
    return this.sendProfileRequest({
      fields: [
        'get_started'
      ]
    }, 'DELETE');
  }

  setPersistentMenu(defaultButtons, defaultComposerInputDisabled, localizedMenues) {
    const formattedButtons = this._formatButtons(defaultButtons);
    return this.sendProfileRequest({
      persistent_menu: [{
        locale: 'default',
        composer_input_disabled: defaultComposerInputDisabled,
        call_to_actions: formattedButtons
      }].concat(localizedMenues)
    });
  }

  deletePersistentMenu() {
    return this.sendProfileRequest({
      fields: [
        'persistent_menu'
      ]
    }, 'DELETE');
  }

  say(recipientId, message, options) {
    if (typeof message === 'string') {
      return this.sendTextMessage(recipientId, message, [], options);
    } else if (message && message.text) {
      if (message.quickReplies && message.quickReplies.length > 0) {
        return this.sendTextMessage(recipientId, message.text, message.quickReplies, options);
      } else if (message.buttons && message.buttons.length > 0) {
        return this.sendButtonTemplate(recipientId, message.text, message.buttons, options);
      }
    } else if (message && message.attachment) {
      return this.sendAttachment(recipientId, message.attachment, message.url, message.quickReplies, options);
    }
    console.error('Invalid format for .say() message.');
  }

  hear(keywords, callback) {
    keywords = Array.isArray(keywords) ? keywords : [ keywords ];
    keywords.forEach(keyword => this._hearMap.push({ keyword, callback }));
    return this;
  }

  module(factory) {
    return factory.apply(this, [ this ]);
  }

  conversation(recipientId, factory) {
    if (!recipientId || !factory || typeof factory !== 'function') {
      return console.error(`You need to specify a recipient and a callback to start a conversation`);
    }
    const convo = new Conversation(this, recipientId);
    this._conversations.push(convo);
    convo.on('end', (endedConvo) => {
      const removeIndex = this._conversations.indexOf(endedConvo);
      this._conversations.splice(removeIndex, 1);
    });
    factory.apply(this, [ convo ]);
    return convo;
  }

  _formatButtons(buttons) {
    return buttons && buttons.map((button) => {
      if (typeof button === 'string') {
        return {
          type: 'postback',
          title: button,
          payload: 'BOOTBOT_BUTTON_' + normalizeString(button)
        };
      } else if (button && button.title) {
        return button;
      }
      return {};
    });
  }

  _formatQuickReplies(quickReplies) {
    return quickReplies && quickReplies.map((reply) => {
      if (typeof reply === 'string') {
        return {
          content_type: 'text',
          title: reply,
          payload: 'BOOTBOT_QR_' + normalizeString(reply)
        };
      } else if (reply && reply.title) {
        return Object.assign({
          content_type: 'text',
          payload:'BOOTBOT_QR_' + normalizeString(reply.title)
        }, reply);
      }
      return reply;
    });
  }

  _handleEvent(type, event, data) {
    const chat = new Chat(this, event.sender.id);
    this.emit(type, event, chat, data);
  }

  _handleMessageEvent(event) {
    if (this._handleConversationResponse('message', event)) { return; }
    const text = event.message.text;
    const senderId = event.sender.id;
    let captured = false;
    if (!text) { return; }

    this._hearMap.forEach(hear => {
      if (typeof hear.keyword === 'string' && hear.keyword.toLowerCase() === text.toLowerCase()) {
        const res = hear.callback.apply(this, [event, new Chat(this, senderId), {
          keyword: hear.keyword,
          captured
        }]);
        captured = true;
        return res;
      } else if (hear.keyword instanceof RegExp && hear.keyword.test(text)) {
        const res = hear.callback.apply(this, [event, new Chat(this, senderId), {
          keyword: hear.keyword,
          match: text.match(hear.keyword),
          captured
        }]);
        captured = true;
        return res;
      }
    });

    this._handleEvent('message', event, { captured });
  }

  _handleAttachmentEvent(event) {
    if (this._handleConversationResponse('attachment', event)) { return; }
    this._handleEvent('attachment', event);
  }

  _handlePostbackEvent(event) {
    if (this._handleConversationResponse('postback', event)) { return; }
    const payload = event.postback.payload;
    if (payload) {
      this._handleEvent(`postback:${payload}`, event);
    }
    this._handleEvent('postback', event);
  }

  _handleQuickReplyEvent(event) {
    if (this._handleConversationResponse('quick_reply', event)) { return; }
    const payload = event.message.quick_reply && event.message.quick_reply.payload;
    if (payload) {
      this._handleEvent(`quick_reply:${payload}`, event);
    }
    this._handleEvent('quick_reply', event);
  }

  _handleConversationResponse(type, event) {
    const userId = event.sender.id;
    let captured = false;
    this._conversations.forEach(convo => {
      if (userId && userId === convo.userId && convo.isActive()) {
        captured = true;
        return convo.respond(event, { type });
      }
    });
    return captured;
  }

  _initWebhook() {
    this.app.get('/webhook', (req, res) => {
      if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === this.verifyToken) {
        console.log('Validation Succeded.')
        res.status(200).send(req.query['hub.challenge']);
      } else {
        console.error('Failed validation. Make sure the validation tokens match.');
        res.sendStatus(403);
      }
    });

    this.app.post('/webhook', (req, res) => {
      var data = req.body;
      if (data.object !== 'page') {
        return;
      }

      // Iterate over each entry. There may be multiple if batched.
      data.entry.forEach((entry) => {
          // Iterate over each messaging event
          entry.messaging.forEach((event) => {
            if (event.message && event.message.is_echo && !this.broadcastEchoes) {
              return;
            }
            if (event.optin) {
              this._handleEvent('authentication', event);
            } else if (event.message && event.message.text) {
              this._handleMessageEvent(event);
              if (event.message.quick_reply) {
                this._handleQuickReplyEvent(event);
              }
            } else if (event.message && event.message.attachments) {
              this._handleAttachmentEvent(event);
            } else if (event.postback) {
              this._handlePostbackEvent(event);
            } else if (event.delivery) {
              this._handleEvent('delivery', event);
            } else if (event.read) {
              this._handleEvent('read', event);
            } else if (event.account_linking) {
              this._handleEvent('account_linking', event);
            } else {
              console.log('Webhook received unknown event: ', event);
            }
          });
        });

        // Must send back a 200 within 20 seconds or the request will time out.
        res.sendStatus(200);
    });
  }

  _verifyRequestSignature(req, res, buf) {
    var signature = req.headers['x-hub-signature'];
    if (!signature) {
      throw new Error('Couldn\'t validate the request signature.');
    } else {
      var elements = signature.split('=');
      var method = elements[0];
      var signatureHash = elements[1];
      var expectedHash = crypto.createHmac('sha1', this.appSecret)
                          .update(buf)
                          .digest('hex');

      if (signatureHash != expectedHash) {
        throw new Error("Couldn't validate the request signature.");
      }
    }
  }
}

module.exports = BootBot;
<|endoftext|>