<commit_before>'use strict';
var EventEmitter = require('events').EventEmitter;
var React = require('react');
var ReactForms = require('react-forms');
var parseAndLogError = require('./mixins').parseAndLogError;
var closest = require('../libs/closest');
var offset = require('../libs/offset');
var ga = require('google-analytics');
var _ = require('underscore');

var filterValue = function(value) {
    if (Array.isArray(value)) {
        value.map(filterValue);
    } else if (typeof value == 'object') {
        _.each(value, function(v, k) {
            if (v === null) {
                delete value[k];
            } else {
                filterValue(v);
            }
        });
    }
};

class JSONNode extends ReactForms.schema.ScalarNode {
    serialize(value) {
        return JSON.stringify(value, null, 4);
    }
    deserialize(value) {
        return (typeof value === 'string') ? JSON.parse(value) : value;
    }
}
module.exports.JSONNode = JSONNode;

var makeValidationResult = function(validation) {
    return new ReactForms.ValidationResult(
        validation.error ? validation.error : null,
        validation.children ? _.mapObject(validation.children, function(v, k) {
            return makeValidationResult(v);
        }) : null
    );
};

var Form = module.exports.Form = React.createClass({
    contextTypes: {
        adviseUnsavedChanges: React.PropTypes.func,
        fetch: React.PropTypes.func
    },

    childContextTypes: {
        canSave: React.PropTypes.func,
        onTriggerSave: React.PropTypes.func,
        formEvents: React.PropTypes.object
    },
    getChildContext: function() {
        return {
            canSave: this.canSave,
            onTriggerSave: this.save,
            formEvents: this.state.formEvents
        };
    },

    getInitialState: function() {
        return {
            isValid: true,
            value: null,
            externalValidation: null,
            formEvents: new EventEmitter()
        };
    },

    componentDidUpdate: function(prevProps, prevState) {
        if (!_.isEqual(prevState.errors, this.state.errors)) {
            var error = document.querySelector('alert-danger');
            if (!error) {
                error = closest(document.querySelector('.rf-Message'), '.rf-Field,.rf-RepeatingFieldset');
            }
            if (error) {
                window.scrollTo(0, offset(error).top - document.getElementById('navbar').clientHeight);
            }
        }
    },

    render: function() {
        return (

                <ReactForms.Form
                    schema={this.props.schema}
                    defaultValue={this.props.defaultValue}
                    externalValidation={this.state.externalValidation}
                    onUpdate={this.handleUpdate} />

                    Cancel
                    {' '}
                    <button onClick={this.save} className="btn btn-success" disabled={!this.canSave()}>Save</button>

                {(this.state.errors || []).map(error =>
{error}
)}

        );
    },

    handleUpdate: function(value, validation) {
        var nextState = {value: value, isValid: validation.isSuccess};
        if (!this.state.unsavedToken) {
            nextState.unsavedToken = this.context.adviseUnsavedChanges();
        }
        this.setState(nextState);
        this.state.formEvents.emit('update');
    },

    canSave: function() {
        return this.state.value && this.state.isValid && !this.state.editor_error && !this.communicating;
    },

    save: function(e) {
        e.preventDefault();
        var value = this.state.value.toJS();
        filterValue(value);
        var method = this.props.method;
        var url = this.props.action;
        var request = this.context.fetch(url, {
            method: method,
            headers: {
                'If-Match': this.props.etag || '*',
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(value)
        });
        request.then(response => {
            if (!response.ok) throw response;
            return response.json();
        })
        .catch(parseAndLogError.bind(undefined, 'putRequest'))
        .then(this.receive);
        this.setState({
            communicating: true,
            putRequest: request
        });
    },

    finish: function (data) {
        if (this.state.unsavedToken) {
            this.state.unsavedToken.release();
            this.setState({unsavedToken: null});
        }
        var url = data['@graph'][0]['@id'];
        this.props.navigate(url);
    },

    receive: function (data) {
        var erred = (data['@type'] || []).indexOf('error') > -1;
        if (erred) {
            return this.showErrors(data);
        } else {
            return this.finish(data);
        }
    },

    showErrors: function (data) {
        // unflatten validation errors
        var externalValidation = {children: {}, error: null};
        var schemaErrors = [];
        if (data.errors !== undefined) {
            data.errors.map(function (error) {
                var name = error.name;
                var match = /u'(\w+)' is a required property/.exec(error.description);
                if (match) {
                    name.push(match[1]);
                }
                if (name.length) {
                    var v = externalValidation;
                    for (var i = 0; i < name.length; i++) {
                        if (v.children[name[i]] === undefined) {
                            v.children[name[i]] = {children: {}, error: null};
                        }
                        v = v.children[name[i]];
                    }
                    v.error = error.description;
                } else {
                    schemaErrors.push(error.description);
                }
            });
        }

        // convert to format expected by react-forms
        externalValidation = makeValidationResult(externalValidation);

        // make sure we scroll to error again
        this.setState({errors: null});

        this.setState({
            data: data,
            communicating: false,
            externalValidation: externalValidation,
            errors: schemaErrors
        });
    }
});
<commit_msg>schema_version is still in the edit frame and needs to be filtered out of the form submission<commit_after>'use strict';
var EventEmitter = require('events').EventEmitter;
var React = require('react');
var ReactForms = require('react-forms');
var parseAndLogError = require('./mixins').parseAndLogError;
var closest = require('../libs/closest');
var offset = require('../libs/offset');
var ga = require('google-analytics');
var _ = require('underscore');

var filterValue = function(value) {
    if (Array.isArray(value)) {
        value.map(filterValue);
    } else if (typeof value == 'object') {
        _.each(value, function(v, k) {
            if (v === null || k == 'schema_version') {
                delete value[k];
            } else {
                filterValue(v);
            }
        });
    }
};

class JSONNode extends ReactForms.schema.ScalarNode {
    serialize(value) {
        return JSON.stringify(value, null, 4);
    }
    deserialize(value) {
        return (typeof value === 'string') ? JSON.parse(value) : value;
    }
}
module.exports.JSONNode = JSONNode;

var makeValidationResult = function(validation) {
    return new ReactForms.ValidationResult(
        validation.error ? validation.error : null,
        validation.children ? _.mapObject(validation.children, function(v, k) {
            return makeValidationResult(v);
        }) : null
    );
};

var Form = module.exports.Form = React.createClass({
    contextTypes: {
        adviseUnsavedChanges: React.PropTypes.func,
        fetch: React.PropTypes.func
    },

    childContextTypes: {
        canSave: React.PropTypes.func,
        onTriggerSave: React.PropTypes.func,
        formEvents: React.PropTypes.object
    },
    getChildContext: function() {
        return {
            canSave: this.canSave,
            onTriggerSave: this.save,
            formEvents: this.state.formEvents
        };
    },

    getInitialState: function() {
        return {
            isValid: true,
            value: null,
            externalValidation: null,
            formEvents: new EventEmitter()
        };
    },

    componentDidUpdate: function(prevProps, prevState) {
        if (!_.isEqual(prevState.errors, this.state.errors)) {
            var error = document.querySelector('alert-danger');
            if (!error) {
                error = closest(document.querySelector('.rf-Message'), '.rf-Field,.rf-RepeatingFieldset');
            }
            if (error) {
                window.scrollTo(0, offset(error).top - document.getElementById('navbar').clientHeight);
            }
        }
    },

    render: function() {
        return (

                <ReactForms.Form
                    schema={this.props.schema}
                    defaultValue={this.props.defaultValue}
                    externalValidation={this.state.externalValidation}
                    onUpdate={this.handleUpdate} />

                    Cancel
                    {' '}
                    <button onClick={this.save} className="btn btn-success" disabled={!this.canSave()}>Save</button>

                {(this.state.errors || []).map(error =>
{error}
)}

        );
    },

    handleUpdate: function(value, validation) {
        var nextState = {value: value, isValid: validation.isSuccess};
        if (!this.state.unsavedToken) {
            nextState.unsavedToken = this.context.adviseUnsavedChanges();
        }
        this.setState(nextState);
        this.state.formEvents.emit('update');
    },

    canSave: function() {
        return this.state.value && this.state.isValid && !this.state.editor_error && !this.communicating;
    },

    save: function(e) {
        e.preventDefault();
        var value = this.state.value.toJS();
        filterValue(value);
        var method = this.props.method;
        var url = this.props.action;
        var request = this.context.fetch(url, {
            method: method,
            headers: {
                'If-Match': this.props.etag || '*',
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(value)
        });
        request.then(response => {
            if (!response.ok) throw response;
            return response.json();
        })
        .catch(parseAndLogError.bind(undefined, 'putRequest'))
        .then(this.receive);
        this.setState({
            communicating: true,
            putRequest: request
        });
    },

    finish: function (data) {
        if (this.state.unsavedToken) {
            this.state.unsavedToken.release();
            this.setState({unsavedToken: null});
        }
        var url = data['@graph'][0]['@id'];
        this.props.navigate(url);
    },

    receive: function (data) {
        var erred = (data['@type'] || []).indexOf('error') > -1;
        if (erred) {
            return this.showErrors(data);
        } else {
            return this.finish(data);
        }
    },

    showErrors: function (data) {
        // unflatten validation errors
        var externalValidation = {children: {}, error: null};
        var schemaErrors = [];
        if (data.errors !== undefined) {
            data.errors.map(function (error) {
                var name = error.name;
                var match = /u'(\w+)' is a required property/.exec(error.description);
                if (match) {
                    name.push(match[1]);
                }
                if (name.length) {
                    var v = externalValidation;
                    for (var i = 0; i < name.length; i++) {
                        if (v.children[name[i]] === undefined) {
                            v.children[name[i]] = {children: {}, error: null};
                        }
                        v = v.children[name[i]];
                    }
                    v.error = error.description;
                } else {
                    schemaErrors.push(error.description);
                }
            });
        }

        // convert to format expected by react-forms
        externalValidation = makeValidationResult(externalValidation);

        // make sure we scroll to error again
        this.setState({errors: null});

        this.setState({
            data: data,
            communicating: false,
            externalValidation: externalValidation,
            errors: schemaErrors
        });
    }
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.dao.history',
  name: 'HistoryRecord',

  documentation: 'Contains an array of property updates',
  ids: [ 'objectId', 'seqNo' ],

  properties: [
    {
      class: 'Long',
      name: 'seqNo'
    },
    {
      class: 'Object',
      name: 'objectId',
      documentation: 'Id of object related to history record.'
    },
    {
      class: 'String',
      name: 'user'
    },
    {
      class: 'DateTime',
      name: 'timestamp',
      documentation: 'Date and time history record was created.'
    },
    {
      class: 'FObjectArray',
      of: 'foam.dao.history.PropertyUpdate',
      name: 'updates',
      documentation: 'Properties updated, contains new and old values.'
    }
  ]
});
<commit_msg>Improve table formatting for HistoryRecord<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.dao.history',
  name: 'HistoryRecord',

  documentation: 'Contains an array of property updates',
  ids: [ 'objectId', 'seqNo' ],

  tableColumns: [
    'timestamp',
    'objectId',
    'user',
    'updates'
  ],

  properties: [
    {
      class: 'Long',
      name: 'seqNo',
      hidden: true
    },
    {
      class: 'Object',
      name: 'objectId',
      label: 'Updated Object',
      documentation: 'Id of object related to history record.',
      tableWidth: 150
    },
    {
      class: 'String',
      name: 'user',
      label: 'Updated By',
      documentation: 'User that made the update.',
      tableWidth: 200
    },
    {
      class: 'DateTime',
      name: 'timestamp',
      documentation: 'Date and time history record was created.',
      tableWidth: 200
    },
    {
      class: 'FObjectArray',
      of: 'foam.dao.history.PropertyUpdate',
      name: 'updates',
      label: 'Updated Properties',
      documentation: 'Properties updated, contains new and old values.'
    }
  ]
});
<|endoftext|>


<commit_before>var embedIds = [];
function embed(data) {
    document.getElementById("hide").style.display = "none";
    document.getElementById("embeds").style.display = "block";
    var ni = document.getElementById('embeds');
    data.embeds.forEach(function(embed) {
        var newdiv = document.createElement('li');
        newdiv.setAttribute('id', embed.date);
        embedIds.push(embed.date);
        embedIds.sort();
        var index = embedIds.indexOf(embed.date);
        var priorLi = null;
        if (index > 0) {
            priorLi = document.getElementById(embedIds[index - 1]);
        } else if (index == 0) {
            priorLi = document.getElementById(embedIds[index]);
        }
        if (embed.type == "lastfm") {
            newdiv.setAttribute('class', 'embedsimple lastfmbg');
            var embednowplaying = "Recently listened to";
            if (embed.nowplaying) {
                embednowplaying = "Now Playing!";
            }
            newdiv.innerHTML = "
" + embednowplaying + " on Last.fm
" + embed.name + " - " + embed.artist + "

";
        } else if (embed.type == "soundcloud") {
            newdiv.setAttribute('class', 'embedsc soundcloudbg');
            newdiv.innerHTML = embed.html;
        } else if (embed.type == "flickr") {
            newdiv.setAttribute('class', 'embedflickr flickrbg');
            var embedtitle = "";
            if (embed.title) {
                embedtitle= "
" + embed.title + "
";
            }
            var embeddescription = "";
            if (embed.description) {
                embeddescription = "
" + embed.description + "
";
            }
            newdiv.innerHTML = "" +
                        "
" + embedtitle + embeddescription + "
From Flickr
" +
                        "

";
        } else if (embed.type == "github") {
            newdiv.setAttribute('class', 'embedsimple githubbg');
            var embedeventtype = "
Did some GitHub activity
";
            if (embed.event_type == "PushEvent") {
                embedeventtype = "";
                embed.payload.commits.forEach(function(commit) {
                    embedeventtype += "" + commit.sha + ":" +
                    "
" + commit.message + "
";
                });
                embedeventtype += "
Pushed to " + embed.repo.name + "
";
            } else if (embed.event_type == "WatchEvent") {
                embedeventtype = "
Started watching " + embed.repo.name + "
";
            } else if (embed.event_type == "CreateEvent") {
                var description = "";
                if (embed.payload.description) {
                    description = "
" + embed.payload.description + "
";
                }
                var ref = embed.payload.ref;
                if (!ref) {
                    ref = "" + embed.repo.name + "";
                }
                embedeventtype = "
Created new " + embed.payload.ref_type + ": " + ref + "" + description +
                    "
On " + embed.repo.name + "";
            } else if (embed.event_type == "CommitCommentEvent") {
                embedeventtype = "
Commented on commit " + embed.payload.comment.commit_id + ":" +
                          "
" + embed.payload.comment.body + "

";
            } else if (embed.event_type == "GollumEvent") {
                embedeventtype = "
Edited
";
                embed.payload.pages.forEach(function(page) {
                    embedeventtype += "
" + page.title + "
";
                });
                embedeventtype += "
On " + embed.repo.name + "
";
            }
            newdiv.innerHTML = "
From GitHub" +
                      "
" + embedeventtype + "

";
        } else if (embed.type == "twitter") {
            newdiv.setAttribute('class', 'embedsimple twitterbg');
            newdiv.innerHTML = embed.html;
        } else {
            newdiv.setAttribute('class', 'embedsimple embedsimplebg');
            newdiv.innerHTML = embed.html;
        }
        if (priorLi) {
            ni.insertBefore(newdiv, priorLi);
        } else {
            ni.appendChild(newdiv);
        }
    });
}
<commit_msg>got the api calls to load in the correct order<commit_after>var embedIds = [];
function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}
function embed(data) {
    document.getElementById("hide").style.display = "none";
    document.getElementById("embeds").style.display = "block";
    var ni = document.getElementById('embeds');
    data.embeds.forEach(function(embed) {
        var newdiv = document.createElement('li');
        newdiv.setAttribute('id', embed.date);
        embedIds.push(embed.date);
        embedIds.sort(function(a, b) {
          return b - a;
        });
        var index = embedIds.indexOf(embed.date);
        var priorLi = null;
        if (index > 0) {
            priorLi = document.getElementById(embedIds[index - 1]);
        } else if (index == 0) {
            priorLi = document.getElementById(embedIds[index]);
        }
        if (embed.type == "lastfm") {
            newdiv.setAttribute('class', 'embedsimple lastfmbg');
            var embednowplaying = "Recently listened to";
            if (embed.nowplaying) {
                embednowplaying = "Now Playing!";
            }
            newdiv.innerHTML = "
" + embednowplaying + " on Last.fm
" + embed.name + " - " + embed.artist + "

";
        } else if (embed.type == "soundcloud") {
            newdiv.setAttribute('class', 'embedsc soundcloudbg');
            newdiv.innerHTML = embed.html;
        } else if (embed.type == "flickr") {
            newdiv.setAttribute('class', 'embedflickr flickrbg');
            var embedtitle = "";
            if (embed.title) {
                embedtitle= "
" + embed.title + "
";
            }
            var embeddescription = "";
            if (embed.description) {
                embeddescription = "
" + embed.description + "
";
            }
            newdiv.innerHTML = "" +
                        "
" + embedtitle + embeddescription + "
From Flickr
" +
                        "

";
        } else if (embed.type == "github") {
            newdiv.setAttribute('class', 'embedsimple githubbg');
            var embedeventtype = "
Did some GitHub activity
";
            if (embed.event_type == "PushEvent") {
                embedeventtype = "";
                embed.payload.commits.forEach(function(commit) {
                    embedeventtype += "" + commit.sha + ":" +
                    "
" + commit.message + "
";
                });
                embedeventtype += "
Pushed to " + embed.repo.name + "
";
            } else if (embed.event_type == "WatchEvent") {
                embedeventtype = "
Started watching " + embed.repo.name + "
";
            } else if (embed.event_type == "CreateEvent") {
                var description = "";
                if (embed.payload.description) {
                    description = "
" + embed.payload.description + "
";
                }
                var ref = embed.payload.ref;
                if (!ref) {
                    ref = "" + embed.repo.name + "";
                }
                embedeventtype = "
Created new " + embed.payload.ref_type + ": " + ref + "" + description +
                    "
On " + embed.repo.name + "";
            } else if (embed.event_type == "CommitCommentEvent") {
                embedeventtype = "
Commented on commit " + embed.payload.comment.commit_id + ":" +
                          "
" + embed.payload.comment.body + "

";
            } else if (embed.event_type == "GollumEvent") {
                embedeventtype = "
Edited
";
                embed.payload.pages.forEach(function(page) {
                    embedeventtype += "
" + page.title + "
";
                });
                embedeventtype += "
On " + embed.repo.name + "
";
            }
            newdiv.innerHTML = "
From GitHub" +
                      "
" + embedeventtype + "

";
        } else if (embed.type == "twitter") {
            newdiv.setAttribute('class', 'embedsimple twitterbg');
            newdiv.innerHTML = embed.html;
        } else {
            newdiv.setAttribute('class', 'embedsimple embedsimplebg');
            newdiv.innerHTML = embed.html;
        }
        if (priorLi) {
            ni.insertBefore(newdiv, priorLi);
        } else {
            ni.appendChild(newdiv);
        }
    });
}
<|endoftext|>


<commit_before>// A thing that emits "entry" events with Reader objects
// Pausing it causes it to stop emitting entry events, and also
// pauses the current entry if there is one.

module.exports = DirReader

var fs = require("graceful-fs")
  , fstream = require("../fstream.js")
  , Reader = fstream.Reader
  , inherits = require("inherits")
  , mkdir = require("mkdirp")
  , path = require("path")
  , Reader = require("./reader.js")

inherits(DirReader, Reader)

function DirReader (props) {
  var me = this
  if (!(me instanceof DirReader)) throw new Error(
    "DirReader must be called as constructor.")

  // should already be established as a Directory type
  if (props.type !== "Directory" || !props.Directory) {
    throw new Error("Non-directory type "+ props.type)
  }

  me.entries = null
  me._index = -1
  me._paused = false
  me._length = -1

  Reader.call(this, props)
}

DirReader.prototype._getEntries = function () {
  var me = this
  fs.readdir(me._path, function (er, entries) {
    if (er) return me.error(er)

    me.entries = entries

    me.emit("entries", entries)
    if (me._paused) me.once("resume", processEntries)
    else processEntries()

    function processEntries () {
      me._length = entries.length
      // console.error("DR %s sort =", me.path, me.props.sort)
      if (typeof me.props.sort === "function") {
        me.entries.sort(me.props.sort)
      }
      me._read()
    }
  })
}

// start walking the dir, and emit an "entry" event for each one.
DirReader.prototype._read = function () {
  var me = this

  if (!me.entries) return me._getEntries()

  if (me._paused || me._currentEntry || me._aborted) {
    // console.error("DR paused=%j, current=%j, aborted=%j", me._paused, !!me._currentEntry, me._aborted)
    return
  }

  me._index ++
  if (me._index >= me._length) {
    if (!me._ended) {
      me._ended = true
      me.emit("end")
      me.emit("close")
    }
    return
  }

  // ok, handle this one, then.

  // save creating a proxy, by stat'ing the thing now.
  var p = path.resolve(me._path, me.entries[me._index])
  // set this to prevent trying to _read() again in the stat time.
  me._currentEntry = p
  fs[ me.props.follow ? "stat" : "lstat" ](p, function (er, stat) {
    if (er) return me.error(er)

    var who = me._proxy || me
    var childProps = me.getChildProps.call(who, stat)
    childProps.path = p

    var entry = Reader(childProps, stat)

    // console.error("DR Entry", p, stat.size)

    me._currentEntry = entry

    // "entry" events are for direct entries in a specific dir.
    // "child" events are for any and all children at all levels.
    // This nomenclature is not completely final.

    entry.on("pause", function (who) {
      if (!me._paused && !entry._disowned) {
        me.pause(who)
      }
    })

    entry.on("resume", function (who) {
      if (me._paused && !entry._disowned) {
        me.resume(who)
      }
    })

    entry.on("ready", function EMITCHILD () {
      // console.error("DR emit child", entry._path)
      if (me._paused) {
        // console.error("  DR emit child - try again later")
        // pause the child, and emit the "entry" event once we drain.
        // console.error("DR pausing child entry")
        entry.pause(me)
        return me.once("resume", EMITCHILD)
      }

      // skip over sockets.  they can't be piped around properly,
      // so there's really no sense even acknowledging them.
      // if someone really wants to see them, they can listen to
      // the "socket" events.
      if (entry.type === "Socket") {
        me.emit("socket", entry)
      } else {
        me.emit("entry", entry)
        me.emit("child", entry)
      }
    })

    var ended = false
    entry.on("close", onend)
    entry.on("disown", onend)
    function onend () {
      if (ended) return
      ended = true
      me.emit("childEnd", entry)
      me.emit("entryEnd", entry)
      me._currentEntry = null
      if (!me._paused) {
        me._read()
      }
    }

    // XXX Remove this.  Works in node as of 0.6.2 or so.
    // Long filenames should not break stuff.
    entry.on("error", function (er) {
      if (entry._swallowErrors) {
        me.warn(er)
        entry.emit("end")
        entry.emit("close")
      } else {
        me.emit("error", er)
      }
    })

    // proxy up some events.
    ; [ "child"
      , "childEnd"
      , "warn"
      ].forEach(function (ev) {
        entry.on(ev, me.emit.bind(me, ev))
      })
  })
}

DirReader.prototype.disown = function (entry) {
  entry.emit("beforeDisown")
  entry._disowned = true
  entry.parent = entry.root = null
  if (entry === this._currentEntry) {
    this._currentEntry = null
  }
  entry.emit("disown")
}

DirReader.prototype.getChildProps = function (stat) {
  return { depth: this.depth + 1
         , root: this.root || this
         , parent: this
         , follow: this.follow
         , filter: this.filter
         , sort: this.props.sort
         }
}

DirReader.prototype.pause = function (who) {
  var me = this
  if (me._paused) return
  who = who || me
  me._paused = true
  if (me._currentEntry && me._currentEntry.pause) {
    me._currentEntry.pause(who)
  }
  me.emit("pause", who)
}

DirReader.prototype.resume = function (who) {
  var me = this
  if (!me._paused) return
  who = who || me

  me._paused = false
  // console.error("DR Emit Resume", me._path)
  me.emit("resume", who)
  if (me._paused) {
    // console.error("DR Re-paused", me._path)
    return
  }

  if (me._currentEntry) {
    if (me._currentEntry.resume) {
      me._currentEntry.resume(who)
    }
  } else me._read()
}
<commit_msg>Regression with 'entries' exposure<commit_after>// A thing that emits "entry" events with Reader objects
// Pausing it causes it to stop emitting entry events, and also
// pauses the current entry if there is one.

module.exports = DirReader

var fs = require("graceful-fs")
  , fstream = require("../fstream.js")
  , Reader = fstream.Reader
  , inherits = require("inherits")
  , mkdir = require("mkdirp")
  , path = require("path")
  , Reader = require("./reader.js")
  , assert = require("assert").ok

inherits(DirReader, Reader)

function DirReader (props) {
  var me = this
  if (!(me instanceof DirReader)) throw new Error(
    "DirReader must be called as constructor.")

  // should already be established as a Directory type
  if (props.type !== "Directory" || !props.Directory) {
    throw new Error("Non-directory type "+ props.type)
  }

  me.entries = null
  me._index = -1
  me._paused = false
  me._length = -1

  Reader.call(this, props)
}

DirReader.prototype._getEntries = function () {
  var me = this
  fs.readdir(me._path, function (er, entries) {
    if (er) return me.error(er)

    me.entries = entries

    me.emit("entries", entries)
    if (me._paused) me.once("resume", processEntries)
    else processEntries()

    function processEntries () {
      me._length = me.entries.length
      // console.error("DR %s sort =", me.path, me.props.sort)
      if (typeof me.props.sort === "function") {
        me.entries.sort(me.props.sort)
      }
      me._read()
    }
  })
}

// start walking the dir, and emit an "entry" event for each one.
DirReader.prototype._read = function () {
  var me = this

  if (!me.entries) return me._getEntries()

  if (me._paused || me._currentEntry || me._aborted) {
    // console.error("DR paused=%j, current=%j, aborted=%j", me._paused, !!me._currentEntry, me._aborted)
    return
  }

  me._index ++
  if (me._index >= me._length) {
    if (!me._ended) {
      me._ended = true
      me.emit("end")
      me.emit("close")
    }
    return
  }

  // ok, handle this one, then.

  // save creating a proxy, by stat'ing the thing now.
  var p = path.resolve(me._path, me.entries[me._index])
  assert(p !== me._path)
  assert(me.entries[me._index])

  // set this to prevent trying to _read() again in the stat time.
  me._currentEntry = p
  fs[ me.props.follow ? "stat" : "lstat" ](p, function (er, stat) {
    if (er) return me.error(er)

    var who = me._proxy || me
    var childProps = me.getChildProps.call(who, stat)
    childProps.path = p

    var entry = Reader(childProps, stat)

    // console.error("DR Entry", p, stat.size)

    me._currentEntry = entry

    // "entry" events are for direct entries in a specific dir.
    // "child" events are for any and all children at all levels.
    // This nomenclature is not completely final.

    entry.on("pause", function (who) {
      if (!me._paused && !entry._disowned) {
        me.pause(who)
      }
    })

    entry.on("resume", function (who) {
      if (me._paused && !entry._disowned) {
        me.resume(who)
      }
    })

    entry.on("ready", function EMITCHILD () {
      // console.error("DR emit child", entry._path)
      if (me._paused) {
        // console.error("  DR emit child - try again later")
        // pause the child, and emit the "entry" event once we drain.
        // console.error("DR pausing child entry")
        entry.pause(me)
        return me.once("resume", EMITCHILD)
      }

      // skip over sockets.  they can't be piped around properly,
      // so there's really no sense even acknowledging them.
      // if someone really wants to see them, they can listen to
      // the "socket" events.
      if (entry.type === "Socket") {
        me.emit("socket", entry)
      } else {
        me.emit("entry", entry)
        me.emit("child", entry)
      }
    })

    var ended = false
    entry.on("close", onend)
    entry.on("disown", onend)
    function onend () {
      if (ended) return
      ended = true
      me.emit("childEnd", entry)
      me.emit("entryEnd", entry)
      me._currentEntry = null
      if (!me._paused) {
        me._read()
      }
    }

    // XXX Remove this.  Works in node as of 0.6.2 or so.
    // Long filenames should not break stuff.
    entry.on("error", function (er) {
      if (entry._swallowErrors) {
        me.warn(er)
        entry.emit("end")
        entry.emit("close")
      } else {
        me.emit("error", er)
      }
    })

    // proxy up some events.
    ; [ "child"
      , "childEnd"
      , "warn"
      ].forEach(function (ev) {
        entry.on(ev, me.emit.bind(me, ev))
      })
  })
}

DirReader.prototype.disown = function (entry) {
  entry.emit("beforeDisown")
  entry._disowned = true
  entry.parent = entry.root = null
  if (entry === this._currentEntry) {
    this._currentEntry = null
  }
  entry.emit("disown")
}

DirReader.prototype.getChildProps = function (stat) {
  return { depth: this.depth + 1
         , root: this.root || this
         , parent: this
         , follow: this.follow
         , filter: this.filter
         , sort: this.props.sort
         }
}

DirReader.prototype.pause = function (who) {
  var me = this
  if (me._paused) return
  who = who || me
  me._paused = true
  if (me._currentEntry && me._currentEntry.pause) {
    me._currentEntry.pause(who)
  }
  me.emit("pause", who)
}

DirReader.prototype.resume = function (who) {
  var me = this
  if (!me._paused) return
  who = who || me

  me._paused = false
  // console.error("DR Emit Resume", me._path)
  me.emit("resume", who)
  if (me._paused) {
    // console.error("DR Re-paused", me._path)
    return
  }

  if (me._currentEntry) {
    if (me._currentEntry.resume) {
      me._currentEntry.resume(who)
    }
  } else me._read()
}
<|endoftext|>


<commit_before>'use strict';

var NODE_FILE = 'file';
var NODE_DIRECTORY = 'directory';

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

function findTree(startDir, filter, cb) {
  var root = {};
  var asyncOps = 0;
  var errored = false;

  function error(err) {
    if (!errored) {
      cb(err);
    }
    errored = true;
  }

  function search(parent, file) {
    parent.path = path.normalize(path.relative(startDir, file)).replace(/\\/g, '/');
    parent.name = path.basename(file);
    parent.displayName = getDisplayName(parent.name);

    asyncOps++;
    fs.stat(file, function (err, stat) {
      if (err) error(err);

      if (stat.isDirectory()) {
        parent.type = NODE_DIRECTORY;
        asyncOps++;
        ireaddir(file, filter, function (err, files) {
          if (err) error(err);

          parent.children = [];
          files.forEach(function (f) {
            var node = {};
            parent.children.push(node);
            search(node, path.join(file, f));
          });
          asyncOps--;
          if (asyncOps == 0) {
            cb(null, root);
          }
        });
      }
      if (stat.isFile()) {
        parent.type = NODE_FILE;
      }
      asyncOps--;
      if (asyncOps == 0) {
        cb(null, root);
      }
    });
  }

  search(root, startDir);
}

function findTreeSync(startDir, filter) {

  function search(file) {
    var node = {
      path: path.normalize(path.relative(startDir, file)).replace(/\\/g, '/'),
      name: path.basename(file)
    };
    node.displayName = getDisplayName(node.name);

    var stats = fs.statSync(file);

    if (stats.isDirectory()) {
      node.type = NODE_DIRECTORY;
      node.children = [];
      var files = ireaddirSync(file, filter);

      files.forEach(function (f) {
        node.children.push(search(path.join(file, f)));
      });
    }
    if (stats.isFile()) {
      node.type = NODE_FILE;
    }

    return node;
  }

  return search(startDir);
}

function getDisplayName(fileName) {
  var withoutUnderscores = fileName.replace(/_/g, ' ');
  var uppercased = withoutUnderscores.charAt(0).toUpperCase() + withoutUnderscores.slice(1);
  return uppercased.replace(/\.feature/g, '');
}

function ireaddirSync(dir, filter) {
  return fs.readdirSync(dir).filter(filter);
}

function ireaddir(dir, filter, callback) {
  fs.readdir(dir, function (err, files) {
    if (err) throw err;

    callback(null, files.filter(filter));
  });
}

module.exports = {
  findTree: findTree,
  findTreeSync: findTreeSync
};
<commit_msg>refactor: fix code style issues<commit_after>'use strict';

var NODE_FILE = 'file';
var NODE_DIRECTORY = 'directory';

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

function findTree(startDir, filter, cb) {
  var root = {};
  var asyncOps = 0;
  var errored = false;

  function error(err) {
    if (!errored) {
      return cb(err);
    }
    errored = true;
  }

  function search(parent, file) {
    parent.path = path.normalize(path.relative(startDir, file)).replace(/\\/g, '/');
    parent.name = path.basename(file);
    parent.displayName = getDisplayName(parent.name);

    asyncOps++;
    fs.stat(file, function (err, stat) {
      if (err) error(err);

      if (stat.isDirectory()) {
        parent.type = NODE_DIRECTORY;
        asyncOps++;
        ireaddir(file, filter, function (err, files) {
          if (err) error(err);

          parent.children = [];
          files.forEach(function (f) {
            var node = {};
            parent.children.push(node);
            search(node, path.join(file, f));
          });
          asyncOps--;
          if (asyncOps === 0) {
            return cb(null, root);
          }
        });
      }
      if (stat.isFile()) {
        parent.type = NODE_FILE;
      }
      asyncOps--;
      if (asyncOps == 0) {
        cb(null, root);
      }
    });
  }

  search(root, startDir);
}

function findTreeSync(startDir, filter) {

  function search(file) {
    var node = {
      path: path.normalize(path.relative(startDir, file)).replace(/\\/g, '/'),
      name: path.basename(file)
    };
    node.displayName = getDisplayName(node.name);

    var stats = fs.statSync(file);

    if (stats.isDirectory()) {
      node.type = NODE_DIRECTORY;
      node.children = [];
      var files = ireaddirSync(file, filter);

      files.forEach(function (f) {
        node.children.push(search(path.join(file, f)));
      });
    }
    if (stats.isFile()) {
      node.type = NODE_FILE;
    }

    return node;
  }

  return search(startDir);
}

function getDisplayName(fileName) {
  var withoutUnderscores = fileName.replace(/_/g, ' ');
  var uppercased = withoutUnderscores.charAt(0).toUpperCase() + withoutUnderscores.slice(1);
  return uppercased.replace(/\.feature/g, '');
}

function ireaddirSync(dir, filter) {
  return fs.readdirSync(dir).filter(filter);
}

function ireaddir(dir, filter, callback) {
  fs.readdir(dir, function (err, files) {
    if (err) throw err;

    callback(null, files.filter(filter));
  });
}

module.exports = {
  findTree: findTree,
  findTreeSync: findTreeSync
};
<|endoftext|>


<commit_before>var errors = require('./errors');
var Bluebird = require('bluebird');
var request = require('request');
var _ = require('lodash');
var querystring = require('querystring');

var VERSION = require('../package.json').version;

var STATUS_MAP = Object.keys(errors).reduce(function(map, key) {
  var error = new errors[key](null);
  if (error.status) {
    map[error.status] = errors[key];
  }
  return map;
}, {});

// TODO: Provide same set of options for each request as for configuration,
// so the config at construction time is just the "defaults"

/**
 * Creates a dispatcher which will act as a basic wrapper for making HTTP
 * requests to the API, and handle authentication.
 * @class
 * @classdesc A HTTP wrapper for the Asana API
 * @param {Object} options for default behavior of the Dispatcher
 * @option {Authenticator} [authenticator] Object to use for authentication.
 *     Can also be set later with `setAuthenticator`.
 * @option {String} [retryOnRateLimit] Automatically handle `RateLimitEnforced`
 *     errors by sleeping and retrying after the waiting period.
 * @option {Function} [handleUnauthorized] Automatically handle
 *     `NoAuthorization` with the callback. If the callback returns `true`
 *     (or a promise resolving to `true), will retry the request.
 * @option {String} [asanaBaseUrl] Base URL for Asana, for debugging
 * @option {Number} [requestTimeout] Timeout (in milliseconds) to wait for the
 *     request to finish.
 */
function Dispatcher(options) {
  options = options || {};
  /**
   * The object to use to handle authentication.
   * @type {Authenticator}
   */
  this.authenticator = options.authenticator || null;
  /**
   * The base URL for Asana
   * @type {String}
   */
  this.asanaBaseUrl = options.asanaBaseUrl || 'https://app.asana.com/';
  /**
   * Whether requests should be automatically retried if rate limited.
   * @type {Boolean}
   */
  this.retryOnRateLimit = options.retryOnRateLimit || false;
  /**
   * What headers should be included by default in all requests, if any.
   * Can be overridden by individual requests.
   * @type {Object}
   */
  this.defaultHeaders = options.defaultHeaders || {};
  /**
   * Handler for unauthorized requests which may seek reauthorization.
   * Default behavior is available if configured with an Oauth authenticator
   * that has a refresh token, and will refresh the current access token.
   * @type {Function}
   */
  this.handleUnauthorized = (options.handleUnauthorized !== undefined) ?
      options.handleUnauthorized : Dispatcher.maybeReauthorize;
  /**
   * Version info gathered from the system, to send with the request.
   * @type {Object}
   */
  this._cachedVersionInfo = null;

  /**
   * The amount of time in milliseconds to wait for a request to finish.
   * @type {Number}
   */
  this.requestTimeout = options.requestTimeout || null;

  /**
   * What proxy should be used by default in all requests, if any.
   * Can be overridden by individual requests.
   * @type {String}
   */
  this.defaultProxy = options.defaultProxy || null;
  /**
   * Whether the dispatcher logs the AsanaChange header or not.
   * @type {boolean}
   */
  this.logAsanaChangeWarnings = options.logAsanaChangeWarnings !== false;
}

/**
 * The relative API path for the current version of the Asana API.
 * @type {String}
 */
Dispatcher.API_PATH = 'api/1.0';

/**
 * Default handler for requests that are considered unauthorized.
 * Requests that the authenticator try to refresh its credentials if
 * possible.
 * @return {Promise<boolean>} True iff refresh was successful, false if not.
 */
Dispatcher.maybeReauthorize = function() {
  if (!this.authenticator) {
    return false;
  }
  return this.authenticator.refreshCredentials();
};

/**
 * Creates an Asana API Url by concatenating the ROOT_URL with path provided.
 * @param  {String} path The path
 * @return {String}      The url
 */
Dispatcher.prototype.url = function(path) {
  return this.asanaBaseUrl + Dispatcher.API_PATH + path;
};

/**
 * Configure the authentication mechanism to use.
 * @returns {Dispatcher} this
 */
Dispatcher.prototype.setAuthenticator = function(authenticator) {
  this.authenticator = authenticator;
  return this;
};

/**
 * Ensure the dispatcher is authorized to make requests. Call this before
 * making any API requests.
 *
 * @returns {Promise} Resolves when the dispatcher is authorized, rejected if
 *     there was a problem authorizing.
 */
Dispatcher.prototype.authorize = function() {
  if (this.authenticator === null) {
    throw new Error('No authenticator configured for dispatcher');
  }
  return this.authenticator.establishCredentials();
};

/**
 * Logs the Asana-Change header if the request is affected and
 * has not Asana-Enabled the flag.
 * @param {Object} reqHeaders   headers in the request
 * @param {Object} resHeaders   headers in the response
 */
Dispatcher.prototype.logAsanaChangeHeader = function(reqHeaders, resHeaders) {
  var changeHeaderKey = null;

  for (var key in resHeaders) {
    if (key.toLowerCase() === 'asana-change') {
      changeHeaderKey = key;
    }
  }

  if (changeHeaderKey !== null) {
    var accountedForFlags = [];

    var addFlag = function(flag) {
      accountedForFlags.push(flag.trim());
    };

    // Grab the request's asana-enable flags
    for (var reqHeader in reqHeaders) {
      if (!reqHeaders.hasOwnProperty(reqHeader)) {
        continue;
      }

      if (reqHeader.toLowerCase() === 'asana-enable') {
        reqHeaders[reqHeader].split(',').forEach(addFlag);
      } else if (reqHeader.toLowerCase() === 'asana-disable') {
        reqHeaders[reqHeader].split(',').forEach(addFlag);
      }
    }

    var changes = resHeaders[changeHeaderKey].split(',');
    changes.forEach(function(unsplitChange) {
      var change = unsplitChange.split(';');

      var name = null;
      var info = null;
      var affected = null;

      for (var j = 0; j < change.length; j++) {
        var field = change[j].split('=');

        field[0] = field[0].trim();
        if (field[0].trim() === 'name') {
          name = field[1].trim();
        }
        else if (field[0].trim() === 'info') {
          info = field[1].trim();
        }
        else if (field[0].trim() === 'affected') {
          affected = field[1].trim();
        }
      }

      // Only show the error if the flag was not in the request's
      // asana-enable header
      if (accountedForFlags.indexOf(name) === -1 && affected === 'true') {
        var message = 'This request is affected by the "' + name +
            '" deprecation. Please visit this url for more info: ' + info +
            '\n' + 'Adding "' + name + '" to your "Asana-Enable" or ' +
            '"Asana-Disable" header will opt in/out to this deprecation ' +
            'and suppress this warning.';

        console.error(message);
      }
    });
  }
};

/**
 * Dispatches a request to the Asana API. The request parameters are passed to
 * the request module.
 * @param  {Object}  params The params for request
 * @param  {Object}  [dispatchOptions] Options for handling request/response
 * @return {Promise}        The response for the request
 */
Dispatcher.prototype.dispatch = function(params, dispatchOptions) {
  var me = this;
  // TODO: actually honor these options as overriding defaults
  dispatchOptions = dispatchOptions || {};

  if (this.requestTimeout) {
    params.timeout = this.requestTimeout;
  }

  if (this.defaultProxy) {
    params.proxy = this.defaultProxy;
  }

  return new Bluebird(function(resolve, reject) {
    function doRequest() {
      if (me.authenticator !== null) {
        me.authenticator.authenticateRequest(params);
      }
      params.headers = _.extend(
          {}, params.headers, me.defaultHeaders, dispatchOptions.headers || {});
      me._addVersionInfo(params);
      request(params, function(err, res, payload) {
        if (err) {
          return reject(err);
        }

        // Handle Asana-Change header logging
        if (me.logAsanaChangeWarnings) {
          me.logAsanaChangeHeader(params.headers, res.headers);
        }

        if (STATUS_MAP[res.statusCode]) {
          var error = new STATUS_MAP[res.statusCode](payload);

          if (me.retryOnRateLimit &&
              error instanceof (errors.RateLimitEnforced)) {
            // Maybe attempt retry for rate limiting.
            // Delay a half-second more in case of rounding error.
            // TODO: verify Asana is always being conservative with duration
            setTimeout(doRequest, error.retryAfterSeconds * 1000 + 500);

          } else if (me.handleUnauthorized &&
              error instanceof (errors.NoAuthorization)) {
            // Maybe attempt to retry unauthorized requests after getting
            // a new access token.
            Bluebird.resolve(me.handleUnauthorized()).then(function(reauth) {
              if (reauth) {
                doRequest();
              } else {
                reject(error);
              }
            })
            .catch(function() {
                reject(error);
            });
          } else {
            // Not an error we can handle.
            return reject(error);
          }
        } else {
          return resolve(payload);
        }
      });
    }
    doRequest();
  });
};

/**
 * Dispatches a GET request to the Asana API.
 * @param  {String}  path    The path of the API
 * @param  {Object}  [query] The query params
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}         The response for the request
 */
Dispatcher.prototype.get = function(path, query, dispatchOptions) {
  var params = {
    method: 'GET',
    url: this.url(path),
    json: true
  };
  var options = this._parseApiOptions(query);
  if (options) {
    params.qs = options;
  }

  if (query) {
    params.qs = Object.assign(query, params.qs);
  }
  return this.dispatch(params, dispatchOptions);
};

/**
 * Dispatches a POST request to the Asana API.
 * @param  {String} path The path of the API
 * @param  {Object} data The data to be sent
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}     The response for the request
 */
Dispatcher.prototype.post = function(path, data, dispatchOptions) {
  var params = {
    method: 'POST',
    url: this.url(path),
    json: {
      data: data
    }
  };
  var options = this._parseApiOptions(data);
  if (options) {
    params.qs = options;
  }

  return this.dispatch(params, dispatchOptions);
};

/**
 * Dispatches a PUT request to the Asana API.
 * @param  {String} path The path of the API
 * @param  {Object} data The data to be sent
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}     The response for the request
 */
Dispatcher.prototype.put = function(path, data, dispatchOptions) {
  var params = {
    method: 'PUT',
    url: this.url(path),
    json: {
      data: data
    }
  };
  var options = this._parseApiOptions(data);
  if (options) {
    params.qs = options;
  }

  return this.dispatch(params, dispatchOptions);
};

/**
 * Dispatches a DELETE request to the Asana API.
 * @param  {String} path The path of the API
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}     The response for the request
 */
Dispatcher.prototype.delete = function(path, dispatchOptions) {
  var params = {
    method: 'DELETE',
    url: this.url(path)
  };

  return this.dispatch(params, dispatchOptions);
};

/**
 * @param request {Object} Request to add version header to.
 * @private
 */
Dispatcher.prototype._addVersionInfo = function(request) {
  if (!this._cachedVersionInfo) {
    this._cachedVersionInfo = this._generateVersionInfo();
  }
  request.headers = request.headers || {};
  request.headers['X-Asana-Client-Lib'] =
      querystring.stringify(this._cachedVersionInfo);
};

/**
 * @return {Object} Dictionary of key-value pairs indicating library version
 * @private
 */
Dispatcher.prototype._generateVersionInfo = function() {
  if (typeof(navigator) === 'undefined' || typeof(window) === 'undefined') {
    return {
      'version': VERSION,
      'language': 'NodeJS',
      'language_version': process.version,
      'os': process.platform
    };
  } else {
    return {
      'version': VERSION,
      'language': 'BrowserJS'
    };
  }
};

Dispatcher.prototype._parseApiOptions = function(params) {
  if (!params) {
    return null;
  }

  var optionKeys = ['pretty', 'fields', 'expand'];
  var options = {};

  for (var i = 0; i < optionKeys.length; i++) {
    if (optionKeys[i] in params) {
      options['opt_' + optionKeys[i]] = params[optionKeys[i]];
    }
  }

  return options;
};

module.exports = Dispatcher;
<commit_msg>Change dispatcher to use indexOf instead of 'in'<commit_after>var errors = require('./errors');
var Bluebird = require('bluebird');
var request = require('request');
var _ = require('lodash');
var querystring = require('querystring');

var VERSION = require('../package.json').version;

var STATUS_MAP = Object.keys(errors).reduce(function(map, key) {
  var error = new errors[key](null);
  if (error.status) {
    map[error.status] = errors[key];
  }
  return map;
}, {});

// TODO: Provide same set of options for each request as for configuration,
// so the config at construction time is just the "defaults"

/**
 * Creates a dispatcher which will act as a basic wrapper for making HTTP
 * requests to the API, and handle authentication.
 * @class
 * @classdesc A HTTP wrapper for the Asana API
 * @param {Object} options for default behavior of the Dispatcher
 * @option {Authenticator} [authenticator] Object to use for authentication.
 *     Can also be set later with `setAuthenticator`.
 * @option {String} [retryOnRateLimit] Automatically handle `RateLimitEnforced`
 *     errors by sleeping and retrying after the waiting period.
 * @option {Function} [handleUnauthorized] Automatically handle
 *     `NoAuthorization` with the callback. If the callback returns `true`
 *     (or a promise resolving to `true), will retry the request.
 * @option {String} [asanaBaseUrl] Base URL for Asana, for debugging
 * @option {Number} [requestTimeout] Timeout (in milliseconds) to wait for the
 *     request to finish.
 */
function Dispatcher(options) {
  options = options || {};
  /**
   * The object to use to handle authentication.
   * @type {Authenticator}
   */
  this.authenticator = options.authenticator || null;
  /**
   * The base URL for Asana
   * @type {String}
   */
  this.asanaBaseUrl = options.asanaBaseUrl || 'https://app.asana.com/';
  /**
   * Whether requests should be automatically retried if rate limited.
   * @type {Boolean}
   */
  this.retryOnRateLimit = options.retryOnRateLimit || false;
  /**
   * What headers should be included by default in all requests, if any.
   * Can be overridden by individual requests.
   * @type {Object}
   */
  this.defaultHeaders = options.defaultHeaders || {};
  /**
   * Handler for unauthorized requests which may seek reauthorization.
   * Default behavior is available if configured with an Oauth authenticator
   * that has a refresh token, and will refresh the current access token.
   * @type {Function}
   */
  this.handleUnauthorized = (options.handleUnauthorized !== undefined) ?
      options.handleUnauthorized : Dispatcher.maybeReauthorize;
  /**
   * Version info gathered from the system, to send with the request.
   * @type {Object}
   */
  this._cachedVersionInfo = null;

  /**
   * The amount of time in milliseconds to wait for a request to finish.
   * @type {Number}
   */
  this.requestTimeout = options.requestTimeout || null;

  /**
   * What proxy should be used by default in all requests, if any.
   * Can be overridden by individual requests.
   * @type {String}
   */
  this.defaultProxy = options.defaultProxy || null;
  /**
   * Whether the dispatcher logs the AsanaChange header or not.
   * @type {boolean}
   */
  this.logAsanaChangeWarnings = options.logAsanaChangeWarnings !== false;
}

/**
 * The relative API path for the current version of the Asana API.
 * @type {String}
 */
Dispatcher.API_PATH = 'api/1.0';

/**
 * Default handler for requests that are considered unauthorized.
 * Requests that the authenticator try to refresh its credentials if
 * possible.
 * @return {Promise<boolean>} True iff refresh was successful, false if not.
 */
Dispatcher.maybeReauthorize = function() {
  if (!this.authenticator) {
    return false;
  }
  return this.authenticator.refreshCredentials();
};

/**
 * Creates an Asana API Url by concatenating the ROOT_URL with path provided.
 * @param  {String} path The path
 * @return {String}      The url
 */
Dispatcher.prototype.url = function(path) {
  return this.asanaBaseUrl + Dispatcher.API_PATH + path;
};

/**
 * Configure the authentication mechanism to use.
 * @returns {Dispatcher} this
 */
Dispatcher.prototype.setAuthenticator = function(authenticator) {
  this.authenticator = authenticator;
  return this;
};

/**
 * Ensure the dispatcher is authorized to make requests. Call this before
 * making any API requests.
 *
 * @returns {Promise} Resolves when the dispatcher is authorized, rejected if
 *     there was a problem authorizing.
 */
Dispatcher.prototype.authorize = function() {
  if (this.authenticator === null) {
    throw new Error('No authenticator configured for dispatcher');
  }
  return this.authenticator.establishCredentials();
};

/**
 * Logs the Asana-Change header if the request is affected and
 * has not Asana-Enabled the flag.
 * @param {Object} reqHeaders   headers in the request
 * @param {Object} resHeaders   headers in the response
 */
Dispatcher.prototype.logAsanaChangeHeader = function(reqHeaders, resHeaders) {
  var changeHeaderKey = null;

  for (var key in resHeaders) {
    if (key.toLowerCase() === 'asana-change') {
      changeHeaderKey = key;
    }
  }

  if (changeHeaderKey !== null) {
    var accountedForFlags = [];

    var addFlag = function(flag) {
      accountedForFlags.push(flag.trim());
    };

    // Grab the request's asana-enable flags
    for (var reqHeader in reqHeaders) {
      if (!reqHeaders.hasOwnProperty(reqHeader)) {
        continue;
      }

      if (reqHeader.toLowerCase() === 'asana-enable') {
        reqHeaders[reqHeader].split(',').forEach(addFlag);
      } else if (reqHeader.toLowerCase() === 'asana-disable') {
        reqHeaders[reqHeader].split(',').forEach(addFlag);
      }
    }

    var changes = resHeaders[changeHeaderKey].split(',');
    changes.forEach(function(unsplitChange) {
      var change = unsplitChange.split(';');

      var name = null;
      var info = null;
      var affected = null;

      for (var j = 0; j < change.length; j++) {
        var field = change[j].split('=');

        field[0] = field[0].trim();
        if (field[0].trim() === 'name') {
          name = field[1].trim();
        }
        else if (field[0].trim() === 'info') {
          info = field[1].trim();
        }
        else if (field[0].trim() === 'affected') {
          affected = field[1].trim();
        }
      }

      // Only show the error if the flag was not in the request's
      // asana-enable header
      if (accountedForFlags.indexOf(name) === -1 && affected === 'true') {
        var message = 'This request is affected by the "' + name +
            '" deprecation. Please visit this url for more info: ' + info +
            '\n' + 'Adding "' + name + '" to your "Asana-Enable" or ' +
            '"Asana-Disable" header will opt in/out to this deprecation ' +
            'and suppress this warning.';

        console.error(message);
      }
    });
  }
};

/**
 * Dispatches a request to the Asana API. The request parameters are passed to
 * the request module.
 * @param  {Object}  params The params for request
 * @param  {Object}  [dispatchOptions] Options for handling request/response
 * @return {Promise}        The response for the request
 */
Dispatcher.prototype.dispatch = function(params, dispatchOptions) {
  var me = this;
  // TODO: actually honor these options as overriding defaults
  dispatchOptions = dispatchOptions || {};

  if (this.requestTimeout) {
    params.timeout = this.requestTimeout;
  }

  if (this.defaultProxy) {
    params.proxy = this.defaultProxy;
  }

  return new Bluebird(function(resolve, reject) {
    function doRequest() {
      if (me.authenticator !== null) {
        me.authenticator.authenticateRequest(params);
      }
      params.headers = _.extend(
          {}, params.headers, me.defaultHeaders, dispatchOptions.headers || {});
      me._addVersionInfo(params);
      request(params, function(err, res, payload) {
        if (err) {
          return reject(err);
        }

        // Handle Asana-Change header logging
        if (me.logAsanaChangeWarnings) {
          me.logAsanaChangeHeader(params.headers, res.headers);
        }

        if (STATUS_MAP[res.statusCode]) {
          var error = new STATUS_MAP[res.statusCode](payload);

          if (me.retryOnRateLimit &&
              error instanceof (errors.RateLimitEnforced)) {
            // Maybe attempt retry for rate limiting.
            // Delay a half-second more in case of rounding error.
            // TODO: verify Asana is always being conservative with duration
            setTimeout(doRequest, error.retryAfterSeconds * 1000 + 500);

          } else if (me.handleUnauthorized &&
              error instanceof (errors.NoAuthorization)) {
            // Maybe attempt to retry unauthorized requests after getting
            // a new access token.
            Bluebird.resolve(me.handleUnauthorized()).then(function(reauth) {
              if (reauth) {
                doRequest();
              } else {
                reject(error);
              }
            })
            .catch(function() {
                reject(error);
            });
          } else {
            // Not an error we can handle.
            return reject(error);
          }
        } else {
          return resolve(payload);
        }
      });
    }
    doRequest();
  });
};

/**
 * Dispatches a GET request to the Asana API.
 * @param  {String}  path    The path of the API
 * @param  {Object}  [query] The query params
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}         The response for the request
 */
Dispatcher.prototype.get = function(path, query, dispatchOptions) {
  var params = {
    method: 'GET',
    url: this.url(path),
    json: true
  };
  var options = this._parseApiOptions(query);
  if (options) {
    params.qs = options;
  }

  if (query) {
    params.qs = Object.assign(query, params.qs);
  }
  return this.dispatch(params, dispatchOptions);
};

/**
 * Dispatches a POST request to the Asana API.
 * @param  {String} path The path of the API
 * @param  {Object} data The data to be sent
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}     The response for the request
 */
Dispatcher.prototype.post = function(path, data, dispatchOptions) {
  var params = {
    method: 'POST',
    url: this.url(path),
    json: {
      data: data
    }
  };
  var options = this._parseApiOptions(data);
  if (options) {
    params.qs = options;
  }

  return this.dispatch(params, dispatchOptions);
};

/**
 * Dispatches a PUT request to the Asana API.
 * @param  {String} path The path of the API
 * @param  {Object} data The data to be sent
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}     The response for the request
 */
Dispatcher.prototype.put = function(path, data, dispatchOptions) {
  var params = {
    method: 'PUT',
    url: this.url(path),
    json: {
      data: data
    }
  };
  var options = this._parseApiOptions(data);
  if (options) {
    params.qs = options;
  }

  return this.dispatch(params, dispatchOptions);
};

/**
 * Dispatches a DELETE request to the Asana API.
 * @param  {String} path The path of the API
 * @param  {Object}  [dispatchOptions] Options for handling the request and
 *     response. See `dispatch`.
 * @return {Promise}     The response for the request
 */
Dispatcher.prototype.delete = function(path, dispatchOptions) {
  var params = {
    method: 'DELETE',
    url: this.url(path)
  };

  return this.dispatch(params, dispatchOptions);
};

/**
 * @param request {Object} Request to add version header to.
 * @private
 */
Dispatcher.prototype._addVersionInfo = function(request) {
  if (!this._cachedVersionInfo) {
    this._cachedVersionInfo = this._generateVersionInfo();
  }
  request.headers = request.headers || {};
  request.headers['X-Asana-Client-Lib'] =
      querystring.stringify(this._cachedVersionInfo);
};

/**
 * @return {Object} Dictionary of key-value pairs indicating library version
 * @private
 */
Dispatcher.prototype._generateVersionInfo = function() {
  if (typeof(navigator) === 'undefined' || typeof(window) === 'undefined') {
    return {
      'version': VERSION,
      'language': 'NodeJS',
      'language_version': process.version,
      'os': process.platform
    };
  } else {
    return {
      'version': VERSION,
      'language': 'BrowserJS'
    };
  }
};

Dispatcher.prototype._parseApiOptions = function(params) {
  if (!params) {
    return null;
  }

  var optionKeys = ['pretty', 'fields', 'expand'];
  var options = {};

  for (var i = 0; i < optionKeys.length; i++) {ß
    if (params.indexOf(optionKeys[i]) !== -1) {
      options['opt_' + optionKeys[i]] = params[optionKeys[i]];
    }
  }

  return options;
};

module.exports = Dispatcher;
<|endoftext|>


<commit_before>var fs = require('fs');
var express = require('express');
var serveStatic = require('serve-static');
var path = require('path');
var pg = require('pg');
var crypto = require('crypto')

var connectMsg = "Info: %s has joined the chatroom.";
var disconnectMsg = "Info: %s has left the chatroom.";
var welcomeMsg = "You are %s!";
var registerMsg = "Register here for your own unique username!";
var userListMsg = "Users online: %s";
var startMsgHistory = "Showing message history.";
var endMsgHistory = "End of message history.";
var commandsMsg = "The following commands are available: %s";

var guestUsername = "Guest_%s";
var colors = ["d-re","l-bl","mage","red","pink","blue","teal","oran","d-pu"];

var commands = {
  "/who":printChannelUserList,
  "/help":printAvailableCommands,
  "/me":selfActionCommand
};
var usersList = {};
var history = {};
var historyLength = 50;

function selfActionCommand(scServer,socket,data) {
  var dataParts = data.msg.split(" ");
  var authToken = socket.getAuthToken();
  if (dataParts.length > 1 && authToken) {
    var action = dataParts.slice(1).join(" ");
    scServer.global.publish(data.channel, {type: "info", msg: "%first %second".replace('%first',authToken.username).replace('%second', action)});
  }
}

function printAvailableCommands(scServer,socket,data) {
  var commandsAvailable = Object.keys(commands).join(', ');
  socket.emit('info', {msg : commandsMsg.replace("%s",commandsAvailable)});
}

function printChannelHistory(scServer,socket,data) {
  if (data.channel in history && history[data.channel].length > 0 && historyLength > 0) {
    socket.emit('info', {msg: startMsgHistory});
    for (var i = 0; i < history[data.channel].length; ++i) {
      socket.emit('info', history[data.channel][i]);
    }
    socket.emit('info', {msg: endMsgHistory});
  }
}

function printChannelUserList(scServer,socket,data) {
  socket.emit('info', {msg: userListMsg.replace("%s",Array.from(new Set(usersList[data.channel])).join(", "))});
}

function htmlEscape(str) {
  return String(str)
  .replace(/&/g, '&')
  .replace(/"/g, '"')
  .replace(/'/g, ''')
  .replace(/</g, '<')
  .replace(/>/g, '>')
  .replace(/\//g, '/');
}

function generateRandomColor() {
  var color = colors[Math.floor(Math.random() * colors.length)];
  return color;
}

Date.prototype.timeNow = function () {
    return ((this.getHours() < 10)?"0":"") + this.getHours() +":"+ ((this.getMinutes() < 10)?"0":"") + this.getMinutes() +":"+ ((this.getSeconds() < 10)?"0":"") + this.getSeconds();
  };

function generateGuestId() {
  var randomInt = Math.floor((Math.random() * 10000) + 1000);
  return guestUsername.replace("%s",randomInt);
}

function getSalt(username,done) {
  var client = new pg.Client(process.env.PUBNUB_DB_URL);
  client.connect();
  var query = client.query("SELECT salt from users where username = $1", [username]);
  var salt = ""
  query.on('row', function(row) {
    salt = row.salt;
    done(salt);
  });
  query.on('end', function() {
    client.end();
  });
}

module.exports.run = function (worker) {
  console.log('   >> Worker PID:', process.pid);
  var app = require('express')();
  var httpServer = worker.httpServer;
  var scServer = worker.scServer;
  app.use(serveStatic(path.resolve(__dirname, 'public')));
  httpServer.on('request', app);

  scServer.on('connection', function (socket) {
    var authToken = socket.getAuthToken();
    //Blocks publish except for server.
    scServer.addMiddleware(scServer.MIDDLEWARE_PUBLISH_IN, function (req, next) {
      next(true);
    });

    socket.on('disconnect', function() {
      //deauth everytime user disconnects.
      socket.deauthenticate();
    })

    socket.on('auth', function(data,res) {
      //Validate user
      var username = generateGuestId();
      if (data.username.substr(0,3) == "reg") {

        usernameParts = data.username.split("_");
        var assumedUsername = usernameParts.slice(1,usernameParts.length-1).join("_");
        getSalt(assumedUsername,function(salt) {

          var hash = crypto.createHmac('sha1', 'chat').update(salt+assumedUsername).digest('hex')
          if (hash == usernameParts[usernameParts.length-1]) {
            username = assumedUsername;
          }
          socket.setAuthToken({username: htmlEscape(username), color: generateRandomColor(), isRegistered: true});
          socket.emit('info', {msg: welcomeMsg.replace("%s",username)});
        });
      } else {
        socket.setAuthToken({username: username, color: generateRandomColor(), isRegistered: false});
        socket.emit('info', {msg: welcomeMsg.replace("%s",username)});
        socket.emit('info', {msg: registerMsg});
      }
    });

    socket.on('chat', function (data) {
      if (!authToken) {
        authToken = socket.getAuthToken();
      }
      if (authToken) {
        var msg = data.msg;
        var cmd = msg.split(" ")[0].toLowerCase();
        if (cmd in commands) {
          commands[cmd](scServer,socket,data);
        } else {
          var time = new Date();
          data.username = authToken.username;
          data.color = authToken.color;
          data.time = time.getTime();
          data.type = "message";
          data.isRegistered = authToken.isRegistered;
          fs.appendFile('/root/log.txt', time.timeNow() + "#" + data.username + "@" + data.channel + ": " + data.msg + "\r\n", function (err) {});
          if (historyLength > 0) {
            if (data.channel in history) {
              history[data.channel].push(data);
              history[data.channel] = history[data.channel].slice(-historyLength);
            } else {
              history[data.channel] = [data];
            }
          }
          scServer.global.publish(data.channel, data);
        }
      }
    });

    socket.on('subscribe', function (data) {
      if (!authToken) {
        authToken = socket.getAuthToken();
      }
      if (authToken){
        if (data in usersList) {
          usersList[data].push(authToken.username);
        } else {
          usersList[data] = [authToken.username];
        }
        printChannelHistory(scServer,socket,{channel:data});
        scServer.global.publish(data, {type: "info", msg: connectMsg.replace('%s',authToken.username)});
        printChannelUserList(scServer,socket,{channel:data});
        printAvailableCommands(scServer,socket,{channel:data});
      }
    });

    socket.on('unsubscribe', function (data) {
      if (!authToken) {
        authToken = socket.getAuthToken();
      }
      if (authToken){
        if (data in usersList) {
          var index = usersList[data].indexOf(authToken.username);
          usersList.splice(index,1);
        }
        scServer.global.publish(data, {type: "info", msg: disconnectMsg.replace('%s',authToken.username)});
      }
    });

  });

};
<commit_msg>Fixed a minor bug<commit_after>var fs = require('fs');
var express = require('express');
var serveStatic = require('serve-static');
var path = require('path');
var pg = require('pg');
var crypto = require('crypto')

var connectMsg = "Info: %s has joined the chatroom.";
var disconnectMsg = "Info: %s has left the chatroom.";
var welcomeMsg = "You are %s!";
var registerMsg = "Register here for your own unique username!";
var userListMsg = "Users online: %s";
var startMsgHistory = "Showing message history.";
var endMsgHistory = "End of message history.";
var commandsMsg = "The following commands are available: %s";

var guestUsername = "Guest_%s";
var colors = ["d-re","l-bl","mage","red","pink","blue","teal","oran","d-pu"];

var commands = {
  "/who":printChannelUserList,
  "/help":printAvailableCommands,
  "/me":selfActionCommand
};
var usersList = {};
var history = {};
var historyLength = 50;

function selfActionCommand(scServer,socket,data) {
  var dataParts = data.msg.split(" ");
  var authToken = socket.getAuthToken();
  if (dataParts.length > 1 && authToken) {
    var action = dataParts.slice(1).join(" ");
    scServer.global.publish(data.channel, {type: "info", msg: "%first %second".replace('%first',authToken.username).replace('%second', action)});
  }
}

function printAvailableCommands(scServer,socket,data) {
  var commandsAvailable = Object.keys(commands).join(', ');
  socket.emit('info', {msg : commandsMsg.replace("%s",commandsAvailable)});
}

function printChannelHistory(scServer,socket,data) {
  if (data.channel in history && history[data.channel].length > 0 && historyLength > 0) {
    socket.emit('info', {msg: startMsgHistory});
    for (var i = 0; i < history[data.channel].length; ++i) {
      socket.emit('info', history[data.channel][i]);
    }
    socket.emit('info', {msg: endMsgHistory});
  }
}

function printChannelUserList(scServer,socket,data) {
  socket.emit('info', {msg: userListMsg.replace("%s",Array.from(new Set(usersList[data.channel])).join(", "))});
}

function htmlEscape(str) {
  return String(str)
  .replace(/&/g, '&')
  .replace(/"/g, '"')
  .replace(/'/g, ''')
  .replace(/</g, '<')
  .replace(/>/g, '>')
  .replace(/\//g, '/');
}

function generateRandomColor() {
  var color = colors[Math.floor(Math.random() * colors.length)];
  return color;
}

Date.prototype.timeNow = function () {
    return ((this.getHours() < 10)?"0":"") + this.getHours() +":"+ ((this.getMinutes() < 10)?"0":"") + this.getMinutes() +":"+ ((this.getSeconds() < 10)?"0":"") + this.getSeconds();
  };

function generateGuestId() {
  var randomInt = Math.floor((Math.random() * 10000) + 1000);
  return guestUsername.replace("%s",randomInt);
}

function getSalt(username,done) {
  var client = new pg.Client(process.env.PUBNUB_DB_URL);
  client.connect();
  var query = client.query("SELECT salt from users where username = $1", [username]);
  var salt = ""
  query.on('row', function(row) {
    salt = row.salt;
    done(salt);
  });
  query.on('end', function() {
    client.end();
  });
}

module.exports.run = function (worker) {
  console.log('   >> Worker PID:', process.pid);
  var app = require('express')();
  var httpServer = worker.httpServer;
  var scServer = worker.scServer;
  app.use(serveStatic(path.resolve(__dirname, 'public')));
  httpServer.on('request', app);

  scServer.on('connection', function (socket) {
    var authToken = socket.getAuthToken();
    //Blocks publish except for server.
    scServer.addMiddleware(scServer.MIDDLEWARE_PUBLISH_IN, function (req, next) {
      next(true);
    });

    socket.on('disconnect', function() {
      //deauth everytime user disconnects.
      socket.deauthenticate();
    })

    socket.on('auth', function(data,res) {
      //Validate user
      var username = generateGuestId();
      if (data.username.substr(0,3) == "reg") {

        usernameParts = data.username.split("_");
        var assumedUsername = usernameParts.slice(1,usernameParts.length-1).join("_");
        getSalt(assumedUsername,function(salt) {

          var hash = crypto.createHmac('sha1', 'chat').update(salt+assumedUsername).digest('hex')
          if (hash == usernameParts[usernameParts.length-1]) {
            username = assumedUsername;
          }
          socket.setAuthToken({username: htmlEscape(username), color: generateRandomColor(), isRegistered: true});
          socket.emit('info', {msg: welcomeMsg.replace("%s",username)});
        });
      } else {
        socket.setAuthToken({username: username, color: generateRandomColor(), isRegistered: false});
        socket.emit('info', {msg: welcomeMsg.replace("%s",username)});
        socket.emit('info', {msg: registerMsg});
      }
    });

    socket.on('chat', function (data) {
      if (!authToken) {
        authToken = socket.getAuthToken();
      }
      if (authToken) {
        var msg = data.msg;
        var cmd = msg.split(" ")[0].toLowerCase();
        if (cmd in commands) {
          commands[cmd](scServer,socket,data);
        } else {
          var time = new Date();
          data.username = authToken.username;
          data.color = authToken.color;
          data.time = time.getTime();
          data.type = "message";
          data.isRegistered = authToken.isRegistered;
          fs.appendFile('/root/log.txt', time.timeNow() + "#" + data.username + "@" + data.channel + ": " + data.msg + "\r\n", function (err) {});
          if (historyLength > 0) {
            if (data.channel in history) {
              history[data.channel].push(data);
              history[data.channel] = history[data.channel].slice(-historyLength);
            } else {
              history[data.channel] = [data];
            }
          }
          scServer.global.publish(data.channel, data);
        }
      }
    });

    socket.on('subscribe', function (data) {
      if (!authToken) {
        authToken = socket.getAuthToken();
      }
      if (authToken){
        if (data in usersList) {
          usersList[data].push(authToken.username);
        } else {
          usersList[data] = [authToken.username];
        }
        printChannelHistory(scServer,socket,{channel:data});
        scServer.global.publish(data, {type: "info", msg: connectMsg.replace('%s',authToken.username)});
        printChannelUserList(scServer,socket,{channel:data});
        printAvailableCommands(scServer,socket,{channel:data});
      }
    });

    socket.on('unsubscribe', function (data) {
      if (!authToken) {
        authToken = socket.getAuthToken();
      }
      if (authToken){
        if (data in usersList) {
          var index = usersList[data].indexOf(authToken.username);
          usersList[data].splice(index,1);
        }
        scServer.global.publish(data, {type: "info", msg: disconnectMsg.replace('%s',authToken.username)});
      }
    });

  });

};
<|endoftext|>


<commit_before>'use strict';

class dispatcher {
    constructor(users, moduleData, systemPrefs) {
        this.users = users;
        this.moduleData = moduleData;
        this.systemPrefs = systemPrefs;
        this.listeners = {};
        this.messageSenders = {};
    }

    addMessageSender(moduleName, callback) {
        this.messageSenders[moduleName] = callback;
    }

    messageRecieved(interfaceName, from, message, interfaceUser) {
        let name;
        message = message.trim();

        if (typeof interfaceUser === 'undefined') {
            interfaceUser = from;
        }

        return this.systemPrefs.get('name').then((instanceName) => {
            name = instanceName;

            return this.users.userFromAccount(interfaceUser, interfaceName);
        }).then((user) => {
            if (user && user.role) {
                return user.role;
            }

            return this.moduleData.get('interface', interfaceName, 'default_permission');
        }).then((role) => {
            if (role && role.length  > 0) {
                return role;
            }

            return this.systemPrefs.get('default_permission');
        }).then((role) => {
            for (let listenerObjectKey in this.listeners) {
                let listener = this.listeners[listenerObjectKey].listener.replace(/\(:<(.+?)>/g, '(');

                let pattern = new RegExp("^" + name + " "+ listener + "$", 'i');

                let match = pattern.exec(message);

                if (match && this.checkRole(role, this.listeners[listenerObjectKey].role)) {
                    match.shift()
                    this.listeners[listenerObjectKey].command(from, interfaceName, match, interfaceUser);
                }
            }
        });
    }

    listen(module, listener, roleNeeded, command, isAlias) {
        if (typeof isAlias === 'undefined') {
            isAlias = false;
        }

        let listenerKey = module.name + listener.replace(
            /(\((:<(.+?)>)*([\(].+?[\)])*(.+?)\))/g,
            (group, groupinner, namegroup, name) => {
                if (typeof name !== 'undefined') {
                    return '<<' + name + '>>'
                }

                return group;
            }
        );

        this.listeners[listenerKey] = {
            module,
            listener,
            role: roleNeeded,
            command,
            isAlias
        }
    }

    sendMessage(to, interfaceName, message) {
        if (this.messageSenders[interfaceName]) {
            this.messageSenders[interfaceName](to, message);
        }
    }

    loadListenerAliases(module) {
        return this.moduleData.get('plugin', module.name, 'listeneraliases').then((aliases) => {
            for (let aliasObject of aliases) {
                let registeredListener = this.listeners[module.name + aliasObject.listener];

                if (registeredListener) {
                    let listenerParts = registeredListener.listener.match(/\((.+?)\)/g);
                    let newPartsOrder = [];
                    let aliasParts = aliasObject.alias.match(/<<(.+?)>>/g);

                    listenerParts.forEach(function(value, i) {
                        let partName = value.match(/:<(.+?)>/);

                        if (!partName) {
                            return;
                        }

                        partName = partName[1];

                        let aliasPartNum = aliasParts.indexOf('<<' + partName + '>>');

                        newPartsOrder[aliasPartNum] = i;

                        aliasObject.alias = aliasObject.alias.replace(aliasParts[aliasPartNum], value);
                    });

                    this.listen(module, aliasObject.alias, registeredListener.role, function(from, interfaceName, params, interfaceUser) {
                        let newParams = [];

                        newPartsOrder.forEach(function(value, i) {
                            newParams[value] = params[i];
                        });

                        registeredListener.command(from, interfaceName, newParams);
                    }, true);
                }
            }
        }).catch(() => {
            // quietly suppress the fact that we don't have listeneraliases for this module
        })
    }

    checkRole(userRole, actionRole) {
        let roles = ['admin', 'trusted', 'standard', 'ignore'];
        let userRoles = roles.slice(roles.indexOf(userRole));

        if (userRoles.indexOf(actionRole) > -1) {
            return true;
        } else {
            return false;
        }
    }
}

module.exports = dispatcher;
<commit_msg>Remove redundant code<commit_after>'use strict';

class dispatcher {
    constructor(users, moduleData, systemPrefs) {
        this.users = users;
        this.moduleData = moduleData;
        this.systemPrefs = systemPrefs;
        this.listeners = {};
        this.messageSenders = {};
    }

    addMessageSender(moduleName, callback) {
        this.messageSenders[moduleName] = callback;
    }

    messageRecieved(interfaceName, from, message, interfaceUser) {
        let name;
        message = message.trim();

        if (typeof interfaceUser === 'undefined') {
            interfaceUser = from;
        }

        return this.systemPrefs.get('name').then((instanceName) => {
            name = instanceName;

            return this.users.userFromAccount(interfaceUser, interfaceName);
        }).then((user) => {
            return user.role;
        }).catch(() => {
            return this.moduleData.get('interface', interfaceName, 'default_permission');
        }).then((role) => {
            if (role && role.length  > 0) {
                return role;
            }

            return this.systemPrefs.get('default_permission');
        }).then((role) => {
            for (let listenerObjectKey in this.listeners) {
                let listener = this.listeners[listenerObjectKey].listener.replace(/\(:<(.+?)>/g, '(');

                let pattern = new RegExp("^" + name + " "+ listener + "$", 'i');

                let match = pattern.exec(message);

                if (match && this.checkRole(role, this.listeners[listenerObjectKey].role)) {
                    match.shift()
                    this.listeners[listenerObjectKey].command(from, interfaceName, match, interfaceUser);
                }
            }
        });
    }

    listen(module, listener, roleNeeded, command, isAlias) {
        if (typeof isAlias === 'undefined') {
            isAlias = false;
        }

        let listenerKey = module.name + listener.replace(
            /(\((:<(.+?)>)*([\(].+?[\)])*(.+?)\))/g,
            (group, groupinner, namegroup, name) => {
                if (typeof name !== 'undefined') {
                    return '<<' + name + '>>'
                }

                return group;
            }
        );

        this.listeners[listenerKey] = {
            module,
            listener,
            role: roleNeeded,
            command,
            isAlias
        }
    }

    sendMessage(to, interfaceName, message) {
        if (this.messageSenders[interfaceName]) {
            this.messageSenders[interfaceName](to, message);
        }
    }

    loadListenerAliases(module) {
        return this.moduleData.get('plugin', module.name, 'listeneraliases').then((aliases) => {
            for (let aliasObject of aliases) {
                let registeredListener = this.listeners[module.name + aliasObject.listener];

                if (registeredListener) {
                    let listenerParts = registeredListener.listener.match(/\((.+?)\)/g);
                    let newPartsOrder = [];
                    let aliasParts = aliasObject.alias.match(/<<(.+?)>>/g);

                    listenerParts.forEach(function(value, i) {
                        let partName = value.match(/:<(.+?)>/);

                        if (!partName) {
                            return;
                        }

                        partName = partName[1];

                        let aliasPartNum = aliasParts.indexOf('<<' + partName + '>>');

                        newPartsOrder[aliasPartNum] = i;

                        aliasObject.alias = aliasObject.alias.replace(aliasParts[aliasPartNum], value);
                    });

                    this.listen(module, aliasObject.alias, registeredListener.role, function(from, interfaceName, params, interfaceUser) {
                        let newParams = [];

                        newPartsOrder.forEach(function(value, i) {
                            newParams[value] = params[i];
                        });

                        registeredListener.command(from, interfaceName, newParams);
                    }, true);
                }
            }
        }).catch(() => {
            // quietly suppress the fact that we don't have listeneraliases for this module
        })
    }

    checkRole(userRole, actionRole) {
        let roles = ['admin', 'trusted', 'standard', 'ignore'];
        let userRoles = roles.slice(roles.indexOf(userRole));

        if (userRoles.indexOf(actionRole) > -1) {
            return true;
        } else {
            return false;
        }
    }
}

module.exports = dispatcher;
<|endoftext|>