<commit_before>/**
 * Module dependencies.
 */
var passport = require('passport')
  , https = require('https')
  , querystring = require('querystring')
  , util = require('util')
  , BadRequestError = require('./errors/badrequesterror')
  , VerificationError = require('./errors/verificationerror');

/**
 * `Strategy` constructor.
 *
 * The BrowserID authentication strategy authenticates requests using the
 * BrowserID JavaScript API and Verified Email Protocol (VEP).
 *
 * BrowserID provides a federated and decentralized universal login system for
 * the web, based on email addresses as an identity token.  Authenticating in
 * this this manner involves a sequence of events, including prompting the user,
 * via their user agent, for an assertion of email address ownership.  Once this
 * assertion is obtained, it can be verified and the user can be authenticated.
 *
 * Applications must supply a `verify` callback which accepts an `email`
 * address, and then calls the `done` callback supplying a `user`, which should
 * be set to `false` if the credentials are not valid.  If an exception occured,
 * `err` should be set.
 *
 * Options:
 *   - `audience`        the website requesting and verifying an identity assertion
 *   - `assertionField`  field name where the assertion is found, defaults to 'assertion'
 *   - `passReqToCallback`     when `true`, `req` is the first argument to the verify callback (default: `false`)
 *
 * Examples:
 *
 *     passport.use(new BrowserIDStrategy({
 *         audience: 'http://www.example.com'
 *       },
 *       function(email, done) {
 *         User.findByEmail(email, function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  if (!options.audience) throw new Error('BrowserID authentication requires an audience option');
  if (!verify) throw new Error('BrowserID authentication strategy requires a verify function');

  passport.Strategy.call(this);
  this.name = 'browserid';
  this._verify = verify;
  this._passReqToCallback = options.passReqToCallback;

  this._audience = options.audience;
  this._assertionField = options.assertionField || 'assertion';

  // options used to inject mock objects for testing purposes
  this._https = options.transport || https;
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(Strategy, passport.Strategy);

/**
 * Authenticate request by using browserid.org as a trusted secondary authority
 * for verifying email assertions.
 *
 * @param {Object} req
 * @api protected
 */
Strategy.prototype.authenticate = function(req) {
  var self = this;

  if (!req.body || !req.body[this._assertionField]) {
    return this.fail(new BadRequestError('Missing assertion'));
  }

  var assertion = req.body[this._assertionField];

  var query = querystring.stringify({ assertion: assertion, audience: this._audience });
  var headers = {};
  headers['Host'] = 'browserid.org';
  headers['Content-Type'] = 'application/x-www-form-urlencoded';
  headers['Content-Length'] = query.length;

  var options = {
    host: 'browserid.org',
    port: 443,
    path: '/verify',
    method: 'POST',
    headers: headers
  };
  var vreq = this._https.request(options, function(res) {
    var data = '';
    res.on('data', function(chunk) {
      data += chunk;
    });
    res.on('end', function() {
      try {
        var result = JSON.parse(data);
        if (result.status === 'okay') {
          return verified(result)
        } else {
          return self.error(new VerificationError(result.reason));
        }
      } catch(e) {
        return self.error(e);
      }
    });
    res.on('error', function(err) {
      return self.error(err);
    });
  });
  vreq.end(query, 'utf8');

  // TODO: Check that the audience matches this server, according to the Host
  //       header.  Also, implement an option to disable this check (defaulting
  //       to false).

  function verified(result) {
    function done(err, user, info) {
      if (err) { return self.error(err); }
      if (!user) { return self.fail(info); }
      self.success(user, info);
    }

    if (self._passReqToCallback) {
      self._verify(req, result.email, done);
    } else {
      self._verify(result.email, done);
    }
  }
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<commit_msg>update the remote verify process<commit_after>/**
 * Module dependencies.
 */
var passport = require('passport')
  , https = require('https')
  , querystring = require('querystring')
  , util = require('util')
  , BadRequestError = require('./errors/badrequesterror')
  , VerificationError = require('./errors/verificationerror');

/**
 * `Strategy` constructor.
 *
 * The BrowserID authentication strategy authenticates requests using the
 * BrowserID JavaScript API and Verified Email Protocol (VEP).
 *
 * BrowserID provides a federated and decentralized universal login system for
 * the web, based on email addresses as an identity token.  Authenticating in
 * this this manner involves a sequence of events, including prompting the user,
 * via their user agent, for an assertion of email address ownership.  Once this
 * assertion is obtained, it can be verified and the user can be authenticated.
 *
 * Applications must supply a `verify` callback which accepts an `email`
 * address, and then calls the `done` callback supplying a `user`, which should
 * be set to `false` if the credentials are not valid.  If an exception occured,
 * `err` should be set.
 *
 * Options:
 *   - `audience`        the website requesting and verifying an identity assertion
 *   - `assertionField`  field name where the assertion is found, defaults to 'assertion'
 *   - `passReqToCallback`     when `true`, `req` is the first argument to the verify callback (default: `false`)
 *
 * Examples:
 *
 *     passport.use(new BrowserIDStrategy({
 *         audience: 'http://www.example.com'
 *       },
 *       function(email, done) {
 *         User.findByEmail(email, function (err, user) {
 *           done(err, user);
 *         });
 *       }
 *     ));
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  if (!options.audience) throw new Error('BrowserID authentication requires an audience option');
  if (!verify) throw new Error('BrowserID authentication strategy requires a verify function');

  passport.Strategy.call(this);
  this.name = 'browserid';
  this._verify = verify;
  this._passReqToCallback = options.passReqToCallback;

  this._audience = options.audience;
  this._assertionField = options.assertionField || 'assertion';

  // options used to inject mock objects for testing purposes
  this._https = options.transport || https;
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(Strategy, passport.Strategy);

/**
 * Authenticate request by using browserid.org as a trusted secondary authority
 * for verifying email assertions.
 *
 * @param {Object} req
 * @api protected
 */
Strategy.prototype.authenticate = function(req) {
  var self = this;

  if (!req.body || !req.body[this._assertionField]) {
    return this.fail(new BadRequestError('Missing assertion'));
  }

  var assertion = req.body[this._assertionField];

  var query = querystring.stringify({ assertion: assertion, audience: this._audience });
  var headers = {};
  headers['Host'] = 'browserid.org';
  headers['Content-Type'] = 'application/x-www-form-urlencoded';
  headers['Content-Length'] = query.length;

  var options = {
    host: 'verifier.login.persona.org',
    path: '/verify',
    method: 'POST',
    headers: headers
  };
  var vreq = this._https.request(options, function(res) {
    var data = '';
    res.on('data', function(chunk) {
      data += chunk;
    });
    res.on('end', function() {
      try {
        var result = JSON.parse(data);
        if (result.status === 'okay') {
          return verified(result)
        } else {
          return self.error(new VerificationError(result.reason));
        }
      } catch(e) {
        return self.error(e);
      }
    });
    res.on('error', function(err) {
      return self.error(err);
    });
  });
  vreq.end(query, 'utf8');

  // TODO: Check that the audience matches this server, according to the Host
  //       header.  Also, implement an option to disable this check (defaulting
  //       to false).

  function verified(result) {
    function done(err, user, info) {
      if (err) { return self.error(err); }
      if (!user) { return self.fail(info); }
      self.success(user, info);
    }

    if (self._passReqToCallback) {
      self._verify(req, result.email, done);
    } else {
      self._verify(result.email, done);
    }
  }
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<|endoftext|>


<commit_before>/*jslint browser: true, undef: true*//*global Ext*/

/**
 * @abstract
 * An abstract class for singletons that facilitates communication with backend services
 *
 * TODO:
 * - add events for all lifecycle events: beforerequest, request, beforexception, exception, unauthorized
 * - does the touch version use Ext.Ajax or parent.request?
 * - pass through request options like touch version does
 */
Ext.define('Jarvus.util.AbstractAPI', {
    extend: 'Ext.data.Connection',

    qualifiedUrlRe: /^(https?:)?\/\//,
    jsonMimeTypeRe: /^application\/([^;\s]+\+)?json(;.+)?$/,

    config: {
        /**
         * @cfg {String/true/null}
         * A host to prefix URLs with, or null to leave paths domain-relative
         */
        host: true,

        /**
         * @cfg {Boolean}
         * True to use HTTPS when prefixing host. Only used if {@link #cfg-host} is set
         */
        useSSL: false,

        /**
         * @cfg {String/null}
         * A path to prefix URLs with
         */
        pathPrefix: null,

        // @inheritdoc
        withCredentials: true,

        // @inheritdoc
        useDefaultXhrHeader: false,

        // @inheritdoc
        disableCaching: false
    },

    constructor: function() {
        var me = this,
            pageParams,
            urlMatch;

        me.callParent(arguments);

        if (me.getHost() === true) {
            pageParams = Ext.Object.fromQueryString(location.search);

            // allow API host to be overridden via apiHost param
            if (pageParams.apiHost && (urlMatch = pageParams.apiHost.match(/(^([a-zA-Z]+):\/\/)?([^/]+).*/))) {
                me.setHost(urlMatch[3]);
                me.setUseSSL('apiSSL' in pageParams ? Boolean(pageParams.apiSSL) : urlMatch[2] == 'https');
            } else {
                me.setHost(null);
                me.setUseSSL(location.protocol === 'https:');
            }
        }
    },

    //@private
    buildUrl: function(path) {
        var me = this,
            host = me.getHost(),
            pathPrefix = me.getPathPrefix();

        if (me.qualifiedUrlRe.test(path)) {
            return path;
        }

        if (pathPrefix) {
            path = pathPrefix + path;
        }

        if (host) {
            path = (me.getUseSSL() ? 'https://' : 'http://') + host + path;
        }

        return path;
    },

    //@private
    buildHeaders: function(headers) {
        return headers;
    },

    //@private
    buildParams: function(params) {
        return params || null;
    },

    /**
     * Override {@link Ext.data.Connection#method-request} to implement auto-decoding and retry handler
     * @inheritdoc
     */
    request: function(options) {
        var me = this,
            jsonMimeTypeRe = me.jsonMimeTypeRe;

        return me.callParent([Ext.applyIf({
            url: me.buildUrl(options.url),
            params: me.buildParams(options.params),
            headers: me.buildHeaders(options.headers),
            timeout: options.timeout || 30000,
            success: function(response) {

                if (options.autoDecode !== false && jsonMimeTypeRe.test(response.getResponseHeader('Content-Type'))) {
                    response.data = Ext.decode(response.responseText, true);
                }

                //Calling the callback function sending the decoded data
                Ext.callback(options.success, options.scope, [response]);

            },
            failure: function(response) {

                if (options.autoDecode !== false && jsonMimeTypeRe.test(response.getResponseHeader('Content-Type'))) {
                    response.data = Ext.decode(response.responseText, true);
                }

                if (response.aborted === true) {
                    Ext.callback(options.abort, options.scope, [response]);
                } else if (response.status == 401 || response.statusText.indexOf('Unauthorized') !== -1) {

                    /*
                    We seem to always get the same session id, so we can't automatically try again once the user logs in
                    var oldSessionID = Ext.util.Cookies.get('s');
                     */

                    Ext.override(Ext.Msg, {
                        hide: function () {
                            var me = this,
                                hideManually = me.cfg ? me.cfg.hideManually : false;

                            if (!hideManually) {
                                me.callParent(arguments);
                            }
                        }
                    });

                    var msg = Ext.Msg.show({
                        hideManually: true,
                        title: 'Login Required',
                        msg: "You've either logged out or your has session expired. Please login and try again.",
                        buttonText: {
                            'yes': 'Login',
                            'no': 'Try Again',
                            'cancel': 'Cancel'
                        },
                        scope: msg,
                        fn: function (btn) {
                            if (btn === 'yes') {
                                // login
                                var loginWindow = window.open(me.buildUrl('/login'), 'emergence-login');
                                loginWindow.focus();
                                return;
                            } else if (btn === 'no') {
                                // try again
                                me.request(options);
                            }

                            msg.cfg.hideManually = false;
                            msg.hide();
                        }
                    });

                    /*
                    if (oldSessionID !== null) {
                        var cookieCheckInterval = window.setInterval(function() {
                            console.log(oldSessionID);
                            console.warn(Ext.util.Cookies.get('s'));
                            if (Ext.util.Cookies.get('s') != oldSessionID) {
                                alert('new login');
                                debugger;
                                window.clearInterval(cookieCheckInterval);
                            }
                        }, 100);
                    }
                    */
                } else if(response.status == 0) {
                    Ext.Msg.confirm('An error occurred', 'There was an error trying to reach the server. Do you want to try again?', function (btn) {
                        if (btn === 'yes') {
                            me.request(options);
                        }
                    });
                } else {
                    Ext.callback(options.failure, options.scope, [response]);
                }

            },
            scope: options.scope
        }, options)]);
    },

    // @deprecated
    setHostname: function(hostname) {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        this.setHost(hostname);
    },

    // @deprecated
    getHostname: function() {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        return this.getHost();
    }
});
<commit_msg>Add promise support to request method<commit_after>/*jslint browser: true, undef: true*//*global Ext*/

/**
 * @abstract
 * An abstract class for singletons that facilitates communication with backend services
 *
 * TODO:
 * - add events for all lifecycle events: beforerequest, request, beforexception, exception, unauthorized
 * - does the touch version use Ext.Ajax or parent.request?
 * - pass through request options like touch version does
 */
Ext.define('Jarvus.util.AbstractAPI', {
    extend: 'Ext.data.Connection',
    requires: [
        'Ext.Promise'
    ],

    qualifiedUrlRe: /^(https?:)?\/\//,
    jsonMimeTypeRe: /^application\/([^;\s]+\+)?json(;.+)?$/,

    config: {
        /**
         * @cfg {String/true/null}
         * A host to prefix URLs with, or null to leave paths domain-relative
         */
        host: true,

        /**
         * @cfg {Boolean}
         * True to use HTTPS when prefixing host. Only used if {@link #cfg-host} is set
         */
        useSSL: false,

        /**
         * @cfg {String/null}
         * A path to prefix URLs with
         */
        pathPrefix: null,

        // @inheritdoc
        withCredentials: true,

        // @inheritdoc
        useDefaultXhrHeader: false,

        // @inheritdoc
        disableCaching: false
    },

    constructor: function() {
        var me = this,
            pageParams,
            urlMatch;

        me.callParent(arguments);

        if (me.getHost() === true) {
            pageParams = Ext.Object.fromQueryString(location.search);

            // allow API host to be overridden via apiHost param
            if (pageParams.apiHost && (urlMatch = pageParams.apiHost.match(/(^([a-zA-Z]+):\/\/)?([^/]+).*/))) {
                me.setHost(urlMatch[3]);
                me.setUseSSL('apiSSL' in pageParams ? Boolean(pageParams.apiSSL) : urlMatch[2] == 'https');
            } else {
                me.setHost(null);
                me.setUseSSL(location.protocol === 'https:');
            }
        }
    },

    //@private
    buildUrl: function(path) {
        var me = this,
            host = me.getHost(),
            pathPrefix = me.getPathPrefix();

        if (me.qualifiedUrlRe.test(path)) {
            return path;
        }

        if (pathPrefix) {
            path = pathPrefix + path;
        }

        if (host) {
            path = (me.getUseSSL() ? 'https://' : 'http://') + host + path;
        }

        return path;
    },

    //@private
    buildHeaders: function(headers) {
        return headers;
    },

    //@private
    buildParams: function(params) {
        return params || null;
    },

    /**
     * Override {@link Ext.data.Connection#method-request} to implement auto-decoding and retry handler
     * @inheritdoc
     */
    request: function(options) {
        var me = this,
            jsonMimeTypeRe = me.jsonMimeTypeRe,
            promise,
            request;

        // build promise if no callbacks provided
        if (!options.callback && !options.success && !options.failure) {
            promise = new Ext.Promise(function (resolve, reject) {
                options.success = resolve;
                options.failure = reject;
            });
        }

        request = me.callParent([Ext.applyIf({
            url: me.buildUrl(options.url),
            params: me.buildParams(options.params),
            headers: me.buildHeaders(options.headers),
            timeout: options.timeout || 30000,
            success: function(response) {

                if (options.autoDecode !== false && jsonMimeTypeRe.test(response.getResponseHeader('Content-Type'))) {
                    response.data = Ext.decode(response.responseText, true);
                }

                //Calling the callback function sending the decoded data
                Ext.callback(options.success, options.scope, [response]);

            },
            failure: function(response) {

                if (options.autoDecode !== false && jsonMimeTypeRe.test(response.getResponseHeader('Content-Type'))) {
                    response.data = Ext.decode(response.responseText, true);
                }

                if (response.aborted === true) {
                    Ext.callback(options.abort, options.scope, [response]);
                } else if (response.status == 401 || response.statusText.indexOf('Unauthorized') !== -1) {

                    /*
                    We seem to always get the same session id, so we can't automatically try again once the user logs in
                    var oldSessionID = Ext.util.Cookies.get('s');
                     */

                    Ext.override(Ext.Msg, {
                        hide: function () {
                            var me = this,
                                hideManually = me.cfg ? me.cfg.hideManually : false;

                            if (!hideManually) {
                                me.callParent(arguments);
                            }
                        }
                    });

                    var msg = Ext.Msg.show({
                        hideManually: true,
                        title: 'Login Required',
                        msg: "You've either logged out or your has session expired. Please login and try again.",
                        buttonText: {
                            'yes': 'Login',
                            'no': 'Try Again',
                            'cancel': 'Cancel'
                        },
                        scope: msg,
                        fn: function (btn) {
                            if (btn === 'yes') {
                                // login
                                var loginWindow = window.open(me.buildUrl('/login'), 'emergence-login');
                                loginWindow.focus();
                                return;
                            } else if (btn === 'no') {
                                // try again
                                me.request(options);
                            }

                            msg.cfg.hideManually = false;
                            msg.hide();
                        }
                    });

                    /*
                    if (oldSessionID !== null) {
                        var cookieCheckInterval = window.setInterval(function() {
                            console.log(oldSessionID);
                            console.warn(Ext.util.Cookies.get('s'));
                            if (Ext.util.Cookies.get('s') != oldSessionID) {
                                alert('new login');
                                debugger;
                                window.clearInterval(cookieCheckInterval);
                            }
                        }, 100);
                    }
                    */
                } else if(response.status == 0) {
                    Ext.Msg.confirm('An error occurred', 'There was an error trying to reach the server. Do you want to try again?', function (btn) {
                        if (btn === 'yes') {
                            me.request(options);
                        }
                    });
                } else {
                    Ext.callback(options.failure, options.scope, [response]);
                }

            },
            scope: options.scope
        }, options)]);

        if (promise) {
            promise.request = request;
            return promise;
        }

        return request;
    },

    // @deprecated
    setHostname: function(hostname) {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        this.setHost(hostname);
    },

    // @deprecated
    getHostname: function() {
        //<debug>
        Ext.Logger.deprecate('hostname config is deprecated, use host instead');
        //</debug>

        return this.getHost();
    }
});
<|endoftext|>


<commit_before>/**
 * @abstract
 * An abstract class for singletons that facilitates communication with backend services on a local or remote emergence server
 */
Ext.define('Emergence.util.AbstractAPI', {
    extend: 'Jarvus.util.AbstractAPI',
    alternateClassName: 'Emergence.ext.util.AbstractAPI',

    config: {
        sessionData: null
    },

    /**
     * @event
     * Fires after successful login
     * @param {Object} sessionData
     */

    buildHeaders: function(headers) {
        headers = headers || {};

        if (!('Accept' in headers)) {
            headers.Accept = 'application/json';
        }

        return headers;
    },

    /**
     * Attempt to load session data
     * @param {String} hostname
     * @param {String} username
     * @param {String} password
     * @param {Function} callback A function to call and pass the new client data to when it is available
     * @param {Object} scope Scope for the callback function
     */
    loadSessionData: function(callback, scope) {
        var me = this;

        me.request({
            url: '/login',
            method: 'GET',
            success: function(response) {
                if (response.data && response.data.success) {
                    me.setSessionData(response.data.data);
                    Ext.callback(callback, scope, [true, response]);
                    me.fireEvent('login', response.data.data);
                } else {
                    Ext.callback(callback, scope, [false, response]);
                }
            },
            exception: function(response) {
                Ext.callback(callback, scope, [false, response]);
            }
        });
    },

    /**
     * Login to a remote Slate instance
     * @param {String} hostname
     * @param {String} username
     * @param {String} password
     * @param {Function} callback A function to call and pass the new client data to when it is available
     * @param {Object} scope Scope for the callback function
     */
    login: function(username, password, callback, scope) {
        var me = this;

        me.request({
            url: '/login',
            params: {
                '_LOGIN[username]': username,
                '_LOGIN[password]': password,
                '_LOGIN[returnMethod]': 'POST'
            },
            success: function(response) {
                if (response.data && response.data.success) {
                    me.setSessionData(response.data.data);
                    Ext.callback(callback, scope, [true, response]);
                    me.fireEvent('login', response.data.data);
                } else {
                    Ext.callback(callback, scope, [false, response]);
                }
            },
            exception: function(response) {
                Ext.callback(callback, scope, [false, response]);
            }
        });
    },

    /**
     * Logout from remote Slate instance
     * @param {Function} callback A function to call and pass the new client data to when it is available
     * @param {Object} scope Scope for the callback function
     */
    logout: function(callback, scope) {
        this.request({
            url: '/login/logout',
            success: function(response) {
                Ext.callback(callback, scope, [true, response]);
                this.fireEvent('logout', response.data);
            },
            exception: function(response) {
                Ext.callback(callback, scope, [false, response]);
            }
        });
    },

    /**
     * Upload an HTML5 file object
     * @param {Object} the html5 file object
     * @param {Function} the function to call after the response
     * @param {Object} the scope for the callback function
     */
    uploadMedia: function(file, callback, scope) {
        var me = this,
            formData = new FormData();

        formData.append('mediaFile', file);

        me.request({
            method: 'POST',
            url: '/media/upload',
            headers: {
                // Setting Content-Type to null allows the browser to automatically set Content-Type.
                // Must-have for file uploads using the FormData object.
                'Content-Type': null
            },
            rawData: formData,
            success: function(response) {
                var success = response.data && response.data.success,
                    mediaData = success && response.data.data;

                Ext.callback(callback, scope, [success, response, mediaData]);
            }
        });
    }
});
<commit_msg>fix: provide failure callbacks for built-in request wrappers<commit_after>/**
 * @abstract
 * An abstract class for singletons that facilitates communication with backend services on a local or remote emergence server
 */
Ext.define('Emergence.util.AbstractAPI', {
    extend: 'Jarvus.util.AbstractAPI',
    alternateClassName: 'Emergence.ext.util.AbstractAPI',

    config: {
        sessionData: null
    },

    /**
     * @event
     * Fires after successful login
     * @param {Object} sessionData
     */

    buildHeaders: function(headers) {
        headers = headers || {};

        if (!('Accept' in headers)) {
            headers.Accept = 'application/json';
        }

        return headers;
    },

    /**
     * Attempt to load session data
     * @param {String} hostname
     * @param {String} username
     * @param {String} password
     * @param {Function} callback A function to call and pass the new client data to when it is available
     * @param {Object} scope Scope for the callback function
     */
    loadSessionData: function(callback, scope) {
        var me = this;

        me.request({
            url: '/login',
            method: 'GET',
            success: function(response) {
                if (response.data && response.data.success) {
                    me.setSessionData(response.data.data);
                    Ext.callback(callback, scope, [true, response]);
                    me.fireEvent('login', response.data.data);
                } else {
                    Ext.callback(callback, scope, [false, response]);
                }
            },
            failure: function(response) {
                Ext.callback(callback, scope, [false, response]);
            },
            exception: function(response) {
                Ext.callback(callback, scope, [false, response]);
            }
        });
    },

    /**
     * Login to a remote Slate instance
     * @param {String} hostname
     * @param {String} username
     * @param {String} password
     * @param {Function} callback A function to call and pass the new client data to when it is available
     * @param {Object} scope Scope for the callback function
     */
    login: function(username, password, callback, scope) {
        var me = this;

        me.request({
            url: '/login',
            params: {
                '_LOGIN[username]': username,
                '_LOGIN[password]': password,
                '_LOGIN[returnMethod]': 'POST'
            },
            success: function(response) {
                if (response.data && response.data.success) {
                    me.setSessionData(response.data.data);
                    Ext.callback(callback, scope, [true, response]);
                    me.fireEvent('login', response.data.data);
                } else {
                    Ext.callback(callback, scope, [false, response]);
                }
            },
            failure: function(response) {
                Ext.callback(callback, scope, [false, response]);
            },
            exception: function(response) {
                Ext.callback(callback, scope, [false, response]);
            }
        });
    },

    /**
     * Logout from remote Slate instance
     * @param {Function} callback A function to call and pass the new client data to when it is available
     * @param {Object} scope Scope for the callback function
     */
    logout: function(callback, scope) {
        this.request({
            url: '/login/logout',
            success: function(response) {
                Ext.callback(callback, scope, [true, response]);
                this.fireEvent('logout', response.data);
            },
            failure: function(response) {
                Ext.callback(callback, scope, [false, response]);
            },
            exception: function(response) {
                Ext.callback(callback, scope, [false, response]);
            }
        });
    },

    /**
     * Upload an HTML5 file object
     * @param {Object} the html5 file object
     * @param {Function} the function to call after the response
     * @param {Object} the scope for the callback function
     */
    uploadMedia: function(file, callback, scope) {
        var me = this,
            formData = new FormData();

        formData.append('mediaFile', file);

        me.request({
            method: 'POST',
            url: '/media/upload',
            headers: {
                // Setting Content-Type to null allows the browser to automatically set Content-Type.
                // Must-have for file uploads using the FormData object.
                'Content-Type': null
            },
            rawData: formData,
            success: function(response) {
                var success = response.data && response.data.success,
                    mediaData = success && response.data.data;

                Ext.callback(callback, scope, [success, response, mediaData]);
            }
        });
    }
});
<|endoftext|>


<commit_before>function extend(destination, source) {

  // JScript DontEnum bug is not take care of
  for (var property in source) {
    destination[property] = source[property];
  }
  return destination;
}

function clone(object) {
  return extend({ }, object);
}

fabric.util.object = {
  extend: extend,
  clone: clone
};<commit_msg>Fix typo.<commit_after>function extend(destination, source) {

  // JScript DontEnum bug is not taken care of
  for (var property in source) {
    destination[property] = source[property];
  }
  return destination;
}

function clone(object) {
  return extend({ }, object);
}

fabric.util.object = {
  extend: extend,
  clone: clone
};<|endoftext|>


<commit_before>{
  "id":10,
  "name":"Wider use of RTI",
  "description": "Lets benefit processing staff get real time employment information about a claimant so that claims can be processed more accurately.",
  "theme": "Fraud & Debt",
  "location": "Leeds",
  "phase":"beta",
  "facing":"internal",
  "sro":"John Fundry",
  "service_man":"Janet Wilkinson",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "September 2014"}
    ],
    "alpha": [
      {"label":"Completed", "date": "December 2014"}
    ],
    "beta": [
      {"label":"Started", "date": "January 2015"},
      {"label":"Private beta started", "date": "February 2016"},
      {"label":"Public beta predicted", "date": "April 2016"}
    ]
  },
  "priority":"Top"
}
<commit_msg>Moved to public beta<commit_after>{
  "id":10,
  "name":"Wider use of RTI",
  "description": "Lets benefit processing staff get real time employment information about a claimant so that claims can be processed more accurately.",
  "theme": "Fraud & Debt",
  "location": "Leeds",
  "phase":"beta",
  "facing":"internal",
  "sro":"John Fundry",
  "service_man":"Janet Wilkinson",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "September 2014"}
    ],
    "alpha": [
      {"label":"Completed", "date": "December 2014"}
    ],
    "beta": [
      {"label":"Started", "date": "January 2015"},
      {"label":"Private beta completed", "date": "April 2016"},
      {"label":"Public beta started", "date": "April 2016"}
    ]
  },
  "priority":"Top"
}
<|endoftext|>


<commit_before>import async from 'async';
import fs from 'fs';
import path from 'path';
import dockerode from 'dockerode';
import _ from 'underscore';
import util from './Util';
import hubUtil from './HubUtil';
import metrics from '../utils/MetricsUtil';
import containerServerActions from '../actions/ContainerServerActions';
import Promise from 'bluebird';
import rimraf from 'rimraf';

export default {
  host: null,
  client: null,
  placeholders: {},

  setup (ip, name) {
    if (!ip || !name) {
      throw new Error('Falsy ip or name passed to docker client setup');
    }

    let certDir = path.join(util.home(), '.docker/machine/machines/', name);
    if (!fs.existsSync(certDir)) {
      throw new Error('Certificate directory does not exist');
    }

    console.log(ip);

    this.host = ip;
    this.client = new dockerode({
      protocol: 'https',
      host: ip,
      port: 2376,
      ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
      cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
      key: fs.readFileSync(path.join(certDir, 'key.pem'))
    });
  },

  init () {
    this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
    this.fetchAllContainers();
    this.listen();

    // Resume pulling containers that were previously being pulled
    _.each(_.values(this.placeholders), container => {
      containerServerActions.added({container});

      this.client.pull(container.Config.Image, (error, stream) => {
        if (error) {
          containerServerActions.error({name: container.Name, error});
          return;
        }

        stream.setEncoding('utf8');
        stream.on('data', function () {});
        stream.on('end', () => {
          if (!this.placeholders[container.Name]) {
            return;
          }

          delete this.placeholders[container.Name];
          localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
          this.createContainer(container.Name, {Image: container.Config.Image});
        });
      });
    });
  },

  startContainer (name, containerData) {
    let startopts = {
      Binds: containerData.Binds || []
    };

    if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
      startopts.PortBindings = containerData.NetworkSettings.Ports;
    } else if (containerData.HostConfig && containerData.HostConfig.PortBindings) {
      startopts.PortBindings = containerData.HostConfig.PortBindings;
    } else {
      startopts.PublishAllPorts = true;
    }

    let container = this.client.getContainer(name);
    container.start(startopts, (error) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }
      containerServerActions.started({name, error});
      this.fetchContainer(name);
    });
  },

  createContainer (name, containerData) {
    containerData.name = containerData.Name || name;

    if (containerData.Config && containerData.Config.Image) {
      containerData.Image = containerData.Config.Image;
    }

    if (!containerData.Env && containerData.Config && containerData.Config.Env) {
      containerData.Env = containerData.Config.Env;
    }

    let existing = this.client.getContainer(name);
    existing.kill(() => {
      existing.remove(() => {
        this.client.createContainer(containerData, (error) => {
          if (error) {
            containerServerActions.error({name, error});
            return;
          }
          metrics.track('Container Finished Creating');
          this.startContainer(name, containerData);
        });
      });
    });
  },

  fetchContainer (id) {
   this.client.getContainer(id).inspect((error, container) => {
      if (error) {
       containerServerActions.error({name: id, error});
      } else {
        container.Name = container.Name.replace('/', '');
        containerServerActions.updated({container});
      }
    });
  },

  fetchAllContainers () {
    this.client.listContainers({all: true}, (err, containers) => {
      if (err) {
        return;
      }
      async.map(containers, (container, callback) => {
        this.client.getContainer(container.Id).inspect((error, container) => {
          container.Name = container.Name.replace('/', '');
          callback(null, container);
        });
      }, (err, containers) => {
        if (err) {
          // TODO: add a global error handler for this
          return;
        }
        containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
      });
    });
  },

  run (name, repository, tag) {
    tag = tag || 'latest';
    let imageName = repository + ':' + tag;

    let placeholderData = {
      Id: util.randomId(),
      Name: name,
      Image: imageName,
      Config: {
        Image: imageName,
      },
      State: {
        Downloading: true
      }
    };
    containerServerActions.added({container: placeholderData});

    this.placeholders[name] = placeholderData;
    localStorage.setItem('placeholders', JSON.stringify(this.placeholders));

    this.pullImage(repository, tag, error => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (!this.placeholders[name]) {
        return;
      }

      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      this.createContainer(name, {Image: imageName});
    },

    // progress is actually the progression PER LAYER (combined in columns)
    // not total because it's not accurate enough
    progress => {
      containerServerActions.progress({name, progress});
    },

    () => {
      containerServerActions.waiting({name, waiting: true});
    });
  },

  updateContainer (name, data) {
    let existing = this.client.getContainer(name);
    existing.inspect((error, existingData) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }
      existingData.name = existingData.Name || name;

      if (existingData.Config && existingData.Config.Image) {
        existingData.Image = existingData.Config.Image;
      }

      if (!existingData.Env && existingData.Config && existingData.Config.Env) {
        existingData.Env = existingData.Config.Env;
      }

      var fullData = _.extend(existingData, data);
      this.createContainer(name, fullData);
    });
  },

  rename (name, newName) {
    this.client.getContainer(name).rename({name: newName}, error => {
      if (error && error.statusCode !== 204) {
        containerServerActions.error({name, error});
        return;
      }
      var oldPath = path.join(util.home(), 'Kitematic', name);
      var newPath = path.join(util.home(), 'Kitematic', newName);

      this.client.getContainer(newName).inspect((error, container) => {
        if (error) {
          // TODO: handle error
          containerServerActions.error({newName, error});
        }
        rimraf(newPath, () => {
          if (fs.existsSync(oldPath)) {
            fs.renameSync(oldPath, newPath);
          }
          var binds = _.pairs(container.Volumes).map(function (pair) {
            return pair[1] + ':' + pair[0];
          });
          var newBinds = binds.map(b => {
            return b.replace(path.join(util.home(), 'Kitematic', name), path.join(util.home(), 'Kitematic', newName));
          });
          this.updateContainer(newName, {Binds: newBinds});
          rimraf(oldPath, () => {});
        });
      });
    });
  },

  restart (name) {
    let container = this.client.getContainer(name);
    container.stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      container.inspect((error, data) => {
        if (error) {
          containerServerActions.error({name, error});
        }
        this.startContainer(name, data);
      });
    });
  },

  stop (name) {
    this.client.getContainer(name).stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  start (name) {
    this.client.getContainer(name).start(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  destroy (name) {
    if (this.placeholders[name]) {
      containerServerActions.destroyed({id: name});
      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      return;
    }

    let container = this.client.getContainer(name);
    container.unpause(function () {
      container.kill(function (error) {
        if (error) {
          containerServerActions.error({name, error});
          return;
        }
        container.remove(function () {
          containerServerActions.destroyed({id: name});
          var volumePath = path.join(util.home(), 'Kitematic', name);
          if (fs.existsSync(volumePath)) {
            rimraf(volumePath, () => {});
          }
        });
      });
    });
  },

  listen () {
    this.client.getEvents((error, stream) => {
      if (error || !stream) {
        // TODO: Add app-wide error handler
        return;
      }

      stream.setEncoding('utf8');
      stream.on('data', json => {
        let data = JSON.parse(json);

        if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete') {
          return;
        }

        if (data.status === 'destroy') {
          containerServerActions.destroyed({name: data.id});
        } else if (data.status === 'create') {
          this.fetchAllContainers();
        } else {
          this.fetchContainer(data.id);
        }
      });
    });
  },

  pullImage (repository, tag, callback, progressCallback, blockedCallback) {
    let opts = {}, config = hubUtil.config();
    if (!hubUtil.config()) {
      opts = {};
    } else {
      let [username, password] = hubUtil.creds(config);
      opts = {
        authconfig: {
          username,
          password,
          auth: ''
        }
      };
    }

    this.client.pull(repository + ':' + tag, opts, (err, stream) => {
      if (err) {
        callback(err);
        return;
      }

      stream.setEncoding('utf8');

      // scheduled to inform about progression at given interval
      let tick = null;
      let layerProgress = {};

      // Split the loading in a few columns for more feedback
      let columns = {};
      columns.amount = 4; // arbitrary
      columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed

      // data is associated with one layer only (can be identified with id)
      stream.on('data', str => {
        var data = JSON.parse(str);

        if (data.error) {
          callback(data.error);
          return;
        }

        if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
          blockedCallback();
          return;
        }

        if (data.status === 'Pulling fs layer') {
          layerProgress[data.id] = {
            current: 0,
            total: 1
          };
        } else if (data.status === 'Downloading') {
          if (!columns.progress) {
            columns.progress = []; // layerIDs, nbLayers, maxLayers, progress value
            let layersToLoad = _.keys(layerProgress).length;
            let layersPerColumn = Math.floor(layersToLoad / columns.amount);
            let leftOverLayers = layersToLoad % columns.amount;
            for (let i = 0; i < columns.amount; i++) {
              let layerAmount = layersPerColumn;
              if (i < leftOverLayers) {
                layerAmount += 1;
              }
              columns.progress[i] = {layerIDs: [], nbLayers:0 , maxLayers: layerAmount, value: 0.0};
            }
          }

          layerProgress[data.id].current = data.progressDetail.current;
          layerProgress[data.id].total = data.progressDetail.total;

          // Assign to a column if not done yet
          if (!layerProgress[data.id].column) {
            // test if we can still add layers to that column
            if (columns.progress[columns.toFill].nbLayers === columns.progress[columns.toFill].maxLayers && columns.toFill < columns.amount - 1) {
              columns.toFill++;
            }

            layerProgress[data.id].column = columns.toFill;
            columns.progress[columns.toFill].layerIDs.push(data.id);
            columns.progress[columns.toFill].nbLayers++;
          }

          if (!tick) {
            tick = setTimeout(() => {
              clearInterval(tick);
              tick = null;
              for (let i = 0; i < columns.amount; i++) {
                columns.progress[i].value = 0.0;
                if (columns.progress[i].nbLayers > 0) {
                  let layer;
                  let totalSum = 0;
                  let currentSum = 0;

                  for (let j = 0; j < columns.progress[i].nbLayers; j++) {
                    layer = layerProgress[columns.progress[i].layerIDs[j]];
                    totalSum += layer.total;
                    currentSum += layer.current;
                  }

                  if (totalSum > 0) {
                    columns.progress[i].value = Math.min(100.0 * currentSum / totalSum, 100);
                  } else {
                    columns.progress[i].value = 0.0;
                  }
                }
              }
              progressCallback(columns);
            }, 16);
          }
        }
      });
      stream.on('end', function () {
        callback();
      });
    });
  },

  // TODO: move this to machine health checks
  waitForConnection (tries, delay) {
    tries = tries || 10;
    delay = delay || 1000;
    let tryCount = 1, connected = false;
    return new Promise((resolve, reject) => {
      async.until(() => connected, callback => {
        this.client.listContainers(error => {
          if (error) {
            if (tryCount > tries) {
              callback(Error('Cannot connect to the Docker Engine. Either the VM is not responding or the connection may be blocked (VPN or Proxy): ' + error.message));
            } else {
              tryCount += 1;
              setTimeout(callback, delay);
            }
          } else {
            connected = true;
            callback();
          }
        });
      }, error => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }
};
<commit_msg>Fixed bug with API requests to 1.7 RC2 /containers/create endpoint<commit_after>import async from 'async';
import fs from 'fs';
import path from 'path';
import dockerode from 'dockerode';
import _ from 'underscore';
import util from './Util';
import hubUtil from './HubUtil';
import metrics from '../utils/MetricsUtil';
import containerServerActions from '../actions/ContainerServerActions';
import Promise from 'bluebird';
import rimraf from 'rimraf';

export default {
  host: null,
  client: null,
  placeholders: {},

  setup (ip, name) {
    if (!ip || !name) {
      throw new Error('Falsy ip or name passed to docker client setup');
    }

    let certDir = path.join(util.home(), '.docker/machine/machines/', name);
    if (!fs.existsSync(certDir)) {
      throw new Error('Certificate directory does not exist');
    }

    console.log(ip);

    this.host = ip;
    this.client = new dockerode({
      protocol: 'https',
      host: ip,
      port: 2376,
      ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
      cert: fs.readFileSync(path.join(certDir, 'cert.pem')),
      key: fs.readFileSync(path.join(certDir, 'key.pem'))
    });
  },

  init () {
    this.placeholders = JSON.parse(localStorage.getItem('placeholders')) || {};
    this.fetchAllContainers();
    this.listen();

    // Resume pulling containers that were previously being pulled
    _.each(_.values(this.placeholders), container => {
      containerServerActions.added({container});

      this.client.pull(container.Config.Image, (error, stream) => {
        if (error) {
          containerServerActions.error({name: container.Name, error});
          return;
        }

        stream.setEncoding('utf8');
        stream.on('data', function () {});
        stream.on('end', () => {
          if (!this.placeholders[container.Name]) {
            return;
          }

          delete this.placeholders[container.Name];
          localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
          this.createContainer(container.Name, {Image: container.Config.Image});
        });
      });
    });
  },

  startContainer (name, containerData) {
    let startopts = {
      Binds: containerData.Binds || []
    };

    if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
      startopts.PortBindings = containerData.NetworkSettings.Ports;
    } else if (containerData.HostConfig && containerData.HostConfig.PortBindings) {
      startopts.PortBindings = containerData.HostConfig.PortBindings;
    } else {
      startopts.PublishAllPorts = true;
    }

    let container = this.client.getContainer(name);
    container.start(startopts, (error) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }
      containerServerActions.started({name, error});
      this.fetchContainer(name);
    });
  },

  createContainer (name, containerData) {
    containerData.name = containerData.Name || name;

    if (containerData.Config && containerData.Config.Image) {
      containerData.Image = containerData.Config.Image;
    }

    if (!containerData.Env && containerData.Config && containerData.Config.Env) {
      containerData.Env = containerData.Config.Env;
    }

    containerData.Volumes = _.mapObject(containerData.Volumes, () => {return {};});

    let existing = this.client.getContainer(name);
    existing.kill(() => {
      existing.remove(() => {
        this.client.createContainer(containerData, (error) => {
          if (error) {
            containerServerActions.error({name, error});
            return;
          }
          metrics.track('Container Finished Creating');
          this.startContainer(name, containerData);
        });
      });
    });
  },

  fetchContainer (id) {
   this.client.getContainer(id).inspect((error, container) => {
      if (error) {
       containerServerActions.error({name: id, error});
      } else {
        container.Name = container.Name.replace('/', '');
        containerServerActions.updated({container});
      }
    });
  },

  fetchAllContainers () {
    this.client.listContainers({all: true}, (err, containers) => {
      if (err) {
        return;
      }
      async.map(containers, (container, callback) => {
        this.client.getContainer(container.Id).inspect((error, container) => {
          container.Name = container.Name.replace('/', '');
          callback(null, container);
        });
      }, (err, containers) => {
        if (err) {
          // TODO: add a global error handler for this
          return;
        }
        containerServerActions.allUpdated({containers: _.indexBy(containers.concat(_.values(this.placeholders)), 'Name')});
      });
    });
  },

  run (name, repository, tag) {
    tag = tag || 'latest';
    let imageName = repository + ':' + tag;

    let placeholderData = {
      Id: util.randomId(),
      Name: name,
      Image: imageName,
      Config: {
        Image: imageName,
      },
      State: {
        Downloading: true
      }
    };
    containerServerActions.added({container: placeholderData});

    this.placeholders[name] = placeholderData;
    localStorage.setItem('placeholders', JSON.stringify(this.placeholders));

    this.pullImage(repository, tag, error => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (!this.placeholders[name]) {
        return;
      }

      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      this.createContainer(name, {Image: imageName});
    },

    // progress is actually the progression PER LAYER (combined in columns)
    // not total because it's not accurate enough
    progress => {
      containerServerActions.progress({name, progress});
    },

    () => {
      containerServerActions.waiting({name, waiting: true});
    });
  },

  updateContainer (name, data) {
    let existing = this.client.getContainer(name);
    existing.inspect((error, existingData) => {
      if (error) {
        containerServerActions.error({name, error});
        return;
      }

      if (existingData.Config && existingData.Config.Image) {
        existingData.Image = existingData.Config.Image;
      }

      if (!existingData.Env && existingData.Config && existingData.Config.Env) {
        existingData.Env = existingData.Config.Env;
      }

      var fullData = _.extend(existingData, data);
      this.createContainer(name, fullData);
    });
  },

  rename (name, newName) {
    this.client.getContainer(name).rename({name: newName}, error => {
      if (error && error.statusCode !== 204) {
        containerServerActions.error({name, error});
        return;
      }
      var oldPath = path.join(util.home(), 'Kitematic', name);
      var newPath = path.join(util.home(), 'Kitematic', newName);

      this.client.getContainer(newName).inspect((error, container) => {
        if (error) {
          // TODO: handle error
          containerServerActions.error({newName, error});
        }
        rimraf(newPath, () => {
          if (fs.existsSync(oldPath)) {
            fs.renameSync(oldPath, newPath);
          }
          var binds = _.pairs(container.Volumes).map(function (pair) {
            return pair[1] + ':' + pair[0];
          });
          var newBinds = binds.map(b => {
            return b.replace(path.join(util.home(), 'Kitematic', name), path.join(util.home(), 'Kitematic', newName));
          });
          this.updateContainer(newName, {Binds: newBinds});
          rimraf(oldPath, () => {});
        });
      });
    });
  },

  restart (name) {
    let container = this.client.getContainer(name);
    container.stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      container.inspect((error, data) => {
        if (error) {
          containerServerActions.error({name, error});
        }
        this.startContainer(name, data);
      });
    });
  },

  stop (name) {
    this.client.getContainer(name).stop(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  start (name) {
    this.client.getContainer(name).start(error => {
      if (error && error.statusCode !== 304) {
        containerServerActions.error({name, error});
        return;
      }
      this.fetchContainer(name);
    });
  },

  destroy (name) {
    if (this.placeholders[name]) {
      containerServerActions.destroyed({id: name});
      delete this.placeholders[name];
      localStorage.setItem('placeholders', JSON.stringify(this.placeholders));
      return;
    }

    let container = this.client.getContainer(name);
    container.unpause(function () {
      container.kill(function (error) {
        if (error) {
          containerServerActions.error({name, error});
          return;
        }
        container.remove(function () {
          containerServerActions.destroyed({id: name});
          var volumePath = path.join(util.home(), 'Kitematic', name);
          if (fs.existsSync(volumePath)) {
            rimraf(volumePath, () => {});
          }
        });
      });
    });
  },

  listen () {
    this.client.getEvents((error, stream) => {
      if (error || !stream) {
        // TODO: Add app-wide error handler
        return;
      }

      stream.setEncoding('utf8');
      stream.on('data', json => {
        let data = JSON.parse(json);

        if (data.status === 'pull' || data.status === 'untag' || data.status === 'delete') {
          return;
        }

        if (data.status === 'destroy') {
          containerServerActions.destroyed({name: data.id});
        } else if (data.status === 'create') {
          this.fetchAllContainers();
        } else {
          this.fetchContainer(data.id);
        }
      });
    });
  },

  pullImage (repository, tag, callback, progressCallback, blockedCallback) {
    let opts = {}, config = hubUtil.config();
    if (!hubUtil.config()) {
      opts = {};
    } else {
      let [username, password] = hubUtil.creds(config);
      opts = {
        authconfig: {
          username,
          password,
          auth: ''
        }
      };
    }

    this.client.pull(repository + ':' + tag, opts, (err, stream) => {
      if (err) {
        callback(err);
        return;
      }

      stream.setEncoding('utf8');

      // scheduled to inform about progression at given interval
      let tick = null;
      let layerProgress = {};

      // Split the loading in a few columns for more feedback
      let columns = {};
      columns.amount = 4; // arbitrary
      columns.toFill = 0; // the current column index, waiting for layer IDs to be displayed

      // data is associated with one layer only (can be identified with id)
      stream.on('data', str => {
        console.log(str);
        var data = JSON.parse(str);

        if (data.error) {
          callback(data.error);
          return;
        }

        if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
          blockedCallback();
          return;
        }

        if (data.status === 'Pulling fs layer') {
          layerProgress[data.id] = {
            current: 0,
            total: 1
          };
        } else if (data.status === 'Downloading') {
          if (!columns.progress) {
            columns.progress = []; // layerIDs, nbLayers, maxLayers, progress value
            let layersToLoad = _.keys(layerProgress).length;
            let layersPerColumn = Math.floor(layersToLoad / columns.amount);
            let leftOverLayers = layersToLoad % columns.amount;
            for (let i = 0; i < columns.amount; i++) {
              let layerAmount = layersPerColumn;
              if (i < leftOverLayers) {
                layerAmount += 1;
              }
              columns.progress[i] = {layerIDs: [], nbLayers:0 , maxLayers: layerAmount, value: 0.0};
            }
          }

          layerProgress[data.id].current = data.progressDetail.current;
          layerProgress[data.id].total = data.progressDetail.total;

          // Assign to a column if not done yet
          if (!layerProgress[data.id].column) {
            // test if we can still add layers to that column
            if (columns.progress[columns.toFill].nbLayers === columns.progress[columns.toFill].maxLayers && columns.toFill < columns.amount - 1) {
              columns.toFill++;
            }

            layerProgress[data.id].column = columns.toFill;
            columns.progress[columns.toFill].layerIDs.push(data.id);
            columns.progress[columns.toFill].nbLayers++;
          }

          if (!tick) {
            tick = setTimeout(() => {
              clearInterval(tick);
              tick = null;
              for (let i = 0; i < columns.amount; i++) {
                columns.progress[i].value = 0.0;
                if (columns.progress[i].nbLayers > 0) {
                  let layer;
                  let totalSum = 0;
                  let currentSum = 0;

                  for (let j = 0; j < columns.progress[i].nbLayers; j++) {
                    layer = layerProgress[columns.progress[i].layerIDs[j]];
                    totalSum += layer.total;
                    currentSum += layer.current;
                  }

                  if (totalSum > 0) {
                    columns.progress[i].value = Math.min(100.0 * currentSum / totalSum, 100);
                  } else {
                    columns.progress[i].value = 0.0;
                  }
                }
              }
              progressCallback(columns);
            }, 16);
          }
        }
      });
      stream.on('end', function () {
        callback();
      });
    });
  },

  // TODO: move this to machine health checks
  waitForConnection (tries, delay) {
    tries = tries || 10;
    delay = delay || 1000;
    let tryCount = 1, connected = false;
    return new Promise((resolve, reject) => {
      async.until(() => connected, callback => {
        this.client.listContainers(error => {
          if (error) {
            if (tryCount > tries) {
              callback(Error('Cannot connect to the Docker Engine. Either the VM is not responding or the connection may be blocked (VPN or Proxy): ' + error.message));
            } else {
              tryCount += 1;
              setTimeout(callback, delay);
            }
          } else {
            connected = true;
            callback();
          }
        });
      }, error => {
        if (error) {
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }
};
<|endoftext|>


<commit_before>{
  "id":9,
  "name":"Repay a benefit debt",
  "description": "Lets users repay money they owe the department so that they can easily manage their debts.",
  "theme": "Fraud & Debt",
  "location": "Leeds",
  "phase":"beta",
  "phase_modifier":"Private",
  "sro":"John Fundry",
  "service_man":"Dave Clayton",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2014"}
    ],
    "alpha": [
      {"label":"Completed", "date": "February 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "June 2015"},
      {"label":"Private beta started", "date": "January 2016"},
      {"label":"Public beta predicted", "date": "June 2016"}
    ]
  },
  "priority":"High"
}
<commit_msg>Location updated<commit_after>{
  "id":9,
  "name":"Repay a benefit debt",
  "description": "Lets users repay money they owe the department so that they can easily manage their debts.",
  "theme": "Fraud & Debt",
  "location": "Stockport",
  "phase":"beta",
  "phase_modifier":"Private",
  "sro":"John Fundry",
  "service_man":"Dave Clayton",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "May 2014"}
    ],
    "alpha": [
      {"label":"Completed", "date": "February 2015"}
    ],
    "beta": [
      {"label":"Started", "date": "June 2015"},
      {"label":"Private beta started", "date": "January 2016"},
      {"label":"Public beta predicted", "date": "June 2016"}
    ]
  },
  "priority":"High"
}
<|endoftext|>


<commit_before>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { EventStatus } from 'matrix-js-sdk';
import MatrixClientPeg from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent";
/**
 * Returns whether an event should allow actions like reply, reactions, edit, etc.
 * which effectively checks whether it's a regular message that has been sent and that we
 * can display.
 *
 * @param {MatrixEvent} mxEvent The event to check
 * @returns {boolean} true if actionable
 */
export function isContentActionable(mxEvent) {
    const { status: eventStatus } = mxEvent;

    // status is SENT before remote-echo, null after
    const isSent = !eventStatus || eventStatus === EventStatus.SENT;

    if (isSent && mxEvent.getType() === 'm.room.message') {
        const content = mxEvent.getContent();
        if (
            content.msgtype &&
            content.msgtype !== 'm.bad.encrypted' &&
            content.hasOwnProperty('body')
        ) {
            return true;
        }
    }

    return false;
}

export function canEditContent(mxEvent) {
    if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message") {
        return false;
    }
    const content = mxEvent.getOriginalContent();
    const {msgtype} = content;
    return (msgtype === "m.text" || msgtype === "m.emote") &&
        mxEvent.getSender() === MatrixClientPeg.get().getUserId();
}

export function canEditOwnEvent(mxEvent) {
    // for now we only allow editing
    // your own events. So this just call through
    // In the future though, moderators will be able to
    // edit other people's messages as well but we don't
    // want findEditableEvent to return other people's events
    // hence this method.
    return canEditContent(mxEvent);
}

const MAX_JUMP_DISTANCE = 100;
export function findEditableEvent(room, isForward, fromEventId = undefined) {
    const liveTimeline = room.getLiveTimeline();
    const events = liveTimeline.getEvents().concat(room.getPendingEvents());
    const maxIdx = events.length - 1;
    const inc = isForward ? 1 : -1;
    const beginIdx = isForward ? 0 : maxIdx;
    let endIdx = isForward ? maxIdx : 0;
    if (!fromEventId) {
        endIdx = Math.min(Math.max(0, beginIdx + (inc * MAX_JUMP_DISTANCE)), maxIdx);
    }
    let foundFromEventId = !fromEventId;
    for (let i = beginIdx; i !== (endIdx + inc); i += inc) {
        const e = events[i];
        // find start event first
        if (!foundFromEventId && e.getId() === fromEventId) {
            foundFromEventId = true;
            // don't look further than MAX_JUMP_DISTANCE events from `fromEventId`
            // to not iterate potentially 1000nds of events on key up/down
            endIdx = Math.min(Math.max(0, i + (inc * MAX_JUMP_DISTANCE)), maxIdx);
        } else if (foundFromEventId && !shouldHideEvent(e) && canEditOwnEvent(e)) {
            // otherwise look for editable event
            return e;
        }
    }
}

<commit_msg>Fix softcrash if editing silly events<commit_after>/*
Copyright 2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { EventStatus } from 'matrix-js-sdk';
import MatrixClientPeg from '../MatrixClientPeg';
import shouldHideEvent from "../shouldHideEvent";
/**
 * Returns whether an event should allow actions like reply, reactions, edit, etc.
 * which effectively checks whether it's a regular message that has been sent and that we
 * can display.
 *
 * @param {MatrixEvent} mxEvent The event to check
 * @returns {boolean} true if actionable
 */
export function isContentActionable(mxEvent) {
    const { status: eventStatus } = mxEvent;

    // status is SENT before remote-echo, null after
    const isSent = !eventStatus || eventStatus === EventStatus.SENT;

    if (isSent && mxEvent.getType() === 'm.room.message') {
        const content = mxEvent.getContent();
        if (
            content.msgtype &&
            content.msgtype !== 'm.bad.encrypted' &&
            content.hasOwnProperty('body')
        ) {
            return true;
        }
    }

    return false;
}

export function canEditContent(mxEvent) {
    if (mxEvent.status === EventStatus.CANCELLED || mxEvent.getType() !== "m.room.message") {
        return false;
    }
    const content = mxEvent.getOriginalContent();
    const {msgtype} = content;
    return (msgtype === "m.text" || msgtype === "m.emote") &&
        content.body && typeof content.body === 'string' &&
        mxEvent.getSender() === MatrixClientPeg.get().getUserId();
}

export function canEditOwnEvent(mxEvent) {
    // for now we only allow editing
    // your own events. So this just call through
    // In the future though, moderators will be able to
    // edit other people's messages as well but we don't
    // want findEditableEvent to return other people's events
    // hence this method.
    return canEditContent(mxEvent);
}

const MAX_JUMP_DISTANCE = 100;
export function findEditableEvent(room, isForward, fromEventId = undefined) {
    const liveTimeline = room.getLiveTimeline();
    const events = liveTimeline.getEvents().concat(room.getPendingEvents());
    const maxIdx = events.length - 1;
    const inc = isForward ? 1 : -1;
    const beginIdx = isForward ? 0 : maxIdx;
    let endIdx = isForward ? maxIdx : 0;
    if (!fromEventId) {
        endIdx = Math.min(Math.max(0, beginIdx + (inc * MAX_JUMP_DISTANCE)), maxIdx);
    }
    let foundFromEventId = !fromEventId;
    for (let i = beginIdx; i !== (endIdx + inc); i += inc) {
        const e = events[i];
        // find start event first
        if (!foundFromEventId && e.getId() === fromEventId) {
            foundFromEventId = true;
            // don't look further than MAX_JUMP_DISTANCE events from `fromEventId`
            // to not iterate potentially 1000nds of events on key up/down
            endIdx = Math.min(Math.max(0, i + (inc * MAX_JUMP_DISTANCE)), maxIdx);
        } else if (foundFromEventId && !shouldHideEvent(e) && canEditOwnEvent(e)) {
            // otherwise look for editable event
            return e;
        }
    }
}

<|endoftext|>


<commit_before>{
  "id":12,
  "name":"Universal Jobmatch",
  "description": "Lets employers advertise job vacancies to unemployed people, to fill positions with appropriately skilled staff.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"alpha",
  "sro":"Kevin Cunnington",
  "service_man":"Richard Knight",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "September 2015"}
    ],
    "alpha": [
      {"label":"Started", "date": "October 2015"}
    ],
    "beta": [
      {"label":"Predicted", "date": "April 2016"},
      {"label":"Private beta predicted", "date": "TBC"},
      {"label":"Public beta predicted", "date": "TBC"}
    ]
  },
  "priority":"Medium"
}
<commit_msg>Update universal-jobmatch.js<commit_after>{
  "id":12,
  "name":"Universal Jobmatch",
  "description": "Lets employers advertise job vacancies to unemployed people, to fill positions with appropriately skilled staff.",
  "theme": "Working Age",
  "location": "Leeds",
  "phase":"alpha",
  "sro":"Kevin Cunnington",
  "service_man":"Richard Knight",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "September 2015"}
    ],
    "alpha": [
      {"label":"Started", "date": "October 2015"},
      {"label":"Expected to finish", "date": "31 March 2016"}
      ]
  },
  "priority":"Medium"
}
<|endoftext|>


<commit_before>import dom from './dom'

/**
 * Class to generate SVG from Pattern objects
 */
class PatternSVG {

  /**
   * Setup the SVG base node
   *
   */
  constructor () {
    this.el = dom.create('svg', {'viewBox': '0 0 ' + this.SVG_WIDTH + ' ' + this.SVG_WIDTH})
  }

  /**
   * Add an invisible rectangle as background.
   * This is only to help Safari to catch touch events
   * on the SVG lock.
   */
  addBackgroundLayer () {
    let rect = dom.create('rect', {
      'fill': '#fff',
      'fill-opacity': '0',
      'width': this.SVG_WIDTH,
      'height': this.SVG_WIDTH
    })
    this.el.appendChild(rect)
    return rect
  }

  /**
   * Add pattern to the instance
   * @param {Pattern}      pattern   Pattern instance to get the points from
   * @param {int}          size      Thickness of the line
   * @param {string|array} color     List of colors to use for the pattern
   * @return PatternSVG
   */
  addPattern (pattern, size = 14, color = '#fff') {
    let lines = []
    color = color instanceof Array ? color : [color]

    for (let i = 1; i < pattern.suite.length; i++) {
      lines.push(dom.create('line', {
        'x1':           (pattern.suite[i-1] % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'y1': Math.floor(pattern.suite[i-1] / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'x2':             (pattern.suite[i] % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'y2':   Math.floor(pattern.suite[i] / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'stroke': color[Math.min(color.length, i) - 1]
      }))
    }

    return this.addGroup({
      'stroke-width': size,
      'stroke-linecap': 'round'
    }, lines)
  }

  /**
   * Add dots to the instance
   * @param {int}    size  Thickness of the line
   * @param {object} attr  List of attributes to set to the group
   * @return PatternSVG
   */
  addDots (size = 3, attr = {}) {
    let dots = []
    attr.fill = attr.fill || '#fff'

    for (let i = 0; i < 9; i++) {
      dots.push(dom.create('circle', {
        cx:    (i % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        cy:    Math.floor(i / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        rel:   i,
        r:     size
      }))
    }
    return this.addGroup(attr, dots)
  }

  /**
   * Add group to the instance
   * @param {object} attr    List of attributes to set to the group
   * @param {*}      content Items as content
   * @return SVGDOMElement
   */
  addGroup (attr, content) {
    let group = dom.create('g', attr, content)
    this.el.appendChild(group)
    return group
  }

  /**
   * Add combinaison results
   * @param {int} goodDots      Amount of good dots
   * @param {int} badPlacedDots Amount of badly placed dots
   * @param {int} wrongDots     Amount of wrong dots
   */
  addCombinaison (goodDots, badPlacedDots, wrongDots) {
    let totalDots = goodDots + badPlacedDots + wrongDots,
        dot = Math.min(Math.floor(this.SVG_WIDTH / totalDots), this.SVG_COMB_EXP),
        dotWidth = Math.floor(dot * .75),
        dotGap = Math.floor(dot * .25),
        xGap = dotWidth + dotGap,
        xStart = Math.floor((this.SVG_WIDTH - (totalDots - 1) * xGap) / 2),
        yStart = this.SVG_WIDTH + Math.floor(this.SVG_COMB_EXP / 2)

    this.el.setAttribute('viewBox', '0 0 ' + this.SVG_WIDTH + ' ' + (this.SVG_WIDTH + this.SVG_COMB_EXP))
    let dots = []

    for (let i = 0; i < totalDots; i++) {
      dots.push(dom.create('circle', {
        'cx': xStart + i * xGap,
        'cy': yStart,
        'r': (dotWidth - this.DOT_BORDER) / 2,
        'stroke-width': this.DOT_BORDER,
        'fill': i < goodDots ? '#fff' : '#000',
        'stroke': i < (goodDots + badPlacedDots) ? '#fff' : '#000',
        'fill-opacity': i < goodDots ? '1' : '.25',
        'stroke-opacity': i < (goodDots + badPlacedDots) ? '1' : '.25'
      }))
    }
    return this.addGroup({}, dots)
  }

  /**
   * Get the SVG
   * @return SVGDOMElement
   */
  getSVG () {
    return this.el
  }
}

PatternSVG.prototype.SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
PatternSVG.prototype.SVG_WIDTH     = 100
PatternSVG.prototype.SVG_COMB_EXP  = 20
PatternSVG.prototype.SVG_MARGIN    = 15
PatternSVG.prototype.GRID_GUTTER   = 35
PatternSVG.prototype.DOT_BORDER    = 2
PatternSVG.prototype.DOT_MAGNET    = 6

export default PatternSVG
<commit_msg>Add final dot to pattern<commit_after>import dom from './dom'

/**
 * Class to generate SVG from Pattern objects
 */
class PatternSVG {

  /**
   * Setup the SVG base node
   *
   */
  constructor () {
    this.el = dom.create('svg', {'viewBox': '0 0 ' + this.SVG_WIDTH + ' ' + this.SVG_WIDTH})
  }

  /**
   * Add an invisible rectangle as background.
   * This is only to help Safari to catch touch events
   * on the SVG lock.
   */
  addBackgroundLayer () {
    let rect = dom.create('rect', {
      'fill': '#fff',
      'fill-opacity': '0',
      'width': this.SVG_WIDTH,
      'height': this.SVG_WIDTH
    })
    this.el.appendChild(rect)
    return rect
  }

  /**
   * Add pattern to the instance
   * @param {Pattern}      pattern   Pattern instance to get the points from
   * @param {int}          size      Thickness of the line
   * @param {string|array} color     List of colors to use for the pattern
   * @return PatternSVG
   */
  addPattern (pattern, size = 14, color = '#fff') {
    let lines = []
    color = color instanceof Array ? color : [color]

    // Add all lines
    for (let i = 1; i < pattern.suite.length; i++) {
      lines.push(dom.create('line', {
        'x1':           (pattern.suite[i-1] % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'y1': Math.floor(pattern.suite[i-1] / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'x2':             (pattern.suite[i] % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'y2':   Math.floor(pattern.suite[i] / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        'stroke': color[Math.min(color.length, i) - 1]
      }))
    }

    // Add the dot reprenting the final dot
    let lastDotIndex = pattern.suite[pattern.suite.length - 1]
    lines.push(dom.create('circle', {
      cx:    (lastDotIndex % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
      cy:    Math.floor(lastDotIndex / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
      fill:  color[0],
      r:     size / 4
    }))

    // Bundle everything in a group
    return this.addGroup({
      'stroke-width': size,
      'stroke-linecap': 'round'
    }, lines)
  }

  /**
   * Add dots to the instance
   * @param {int}    size  Thickness of the line
   * @param {object} attr  List of attributes to set to the group
   * @return PatternSVG
   */
  addDots (size = 3, attr = {}) {
    let dots = []
    attr.fill = attr.fill || '#fff'

    for (let i = 0; i < 9; i++) {
      dots.push(dom.create('circle', {
        cx:    (i % 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        cy:    Math.floor(i / 3) * this.GRID_GUTTER + this.SVG_MARGIN,
        rel:   i,
        r:     size
      }))
    }
    return this.addGroup(attr, dots)
  }

  /**
   * Add group to the instance
   * @param {object} attr    List of attributes to set to the group
   * @param {*}      content Items as content
   * @return SVGDOMElement
   */
  addGroup (attr, content) {
    let group = dom.create('g', attr, content)
    this.el.appendChild(group)
    return group
  }

  /**
   * Add combinaison results
   * @param {int} goodDots      Amount of good dots
   * @param {int} badPlacedDots Amount of badly placed dots
   * @param {int} wrongDots     Amount of wrong dots
   */
  addCombinaison (goodDots, badPlacedDots, wrongDots) {
    let totalDots = goodDots + badPlacedDots + wrongDots,
        dot = Math.min(Math.floor(this.SVG_WIDTH / totalDots), this.SVG_COMB_EXP),
        dotWidth = Math.floor(dot * .75),
        dotGap = Math.floor(dot * .25),
        xGap = dotWidth + dotGap,
        xStart = Math.floor((this.SVG_WIDTH - (totalDots - 1) * xGap) / 2),
        yStart = this.SVG_WIDTH + Math.floor(this.SVG_COMB_EXP / 2)

    this.el.setAttribute('viewBox', '0 0 ' + this.SVG_WIDTH + ' ' + (this.SVG_WIDTH + this.SVG_COMB_EXP))
    let dots = []

    for (let i = 0; i < totalDots; i++) {
      dots.push(dom.create('circle', {
        'cx': xStart + i * xGap,
        'cy': yStart,
        'r': (dotWidth - this.DOT_BORDER) / 2,
        'stroke-width': this.DOT_BORDER,
        'fill': i < goodDots ? '#fff' : '#000',
        'stroke': i < (goodDots + badPlacedDots) ? '#fff' : '#000',
        'fill-opacity': i < goodDots ? '1' : '.25',
        'stroke-opacity': i < (goodDots + badPlacedDots) ? '1' : '.25'
      }))
    }
    return this.addGroup({}, dots)
  }

  /**
   * Get the SVG
   * @return SVGDOMElement
   */
  getSVG () {
    return this.el
  }
}

PatternSVG.prototype.SVG_NAMESPACE = 'http://www.w3.org/2000/svg'
PatternSVG.prototype.SVG_WIDTH     = 100
PatternSVG.prototype.SVG_COMB_EXP  = 20
PatternSVG.prototype.SVG_MARGIN    = 15
PatternSVG.prototype.GRID_GUTTER   = 35
PatternSVG.prototype.DOT_BORDER    = 2
PatternSVG.prototype.DOT_MAGNET    = 6

export default PatternSVG
<|endoftext|>


<commit_before>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import Trait from '../traits/Trait';

/**
 * Tests if the parent is a parent of child
 * @param {function|object} parent - Instance or Class
 * @param {function|object} child - Instance or Class
 * @returns {boolean} - is parent a parent of child
 * @private
 * @example
 * class A {}
 * class B extends A {}
 * class C extends B {}
 *
 * isParentOf(A, C) // true
 * isParentOf(B, C) // true
 * isParentOf(C, C) // true
 * isParentOf(B, A) // false
 */
export function isParentOf(parent, child) {
  parent = typeof parent === 'function' ? parent : parent.constructor;
  child = typeof child === 'function' ? child : child.constructor;

  do {
    if (child.name === parent.name) {
      return true;
    }

    child = Object.getPrototypeOf(child);
  } while (child.name !== '');

  return false;
}

/**
 * Get the name of the value type
 * @param {*} value - Any value
 * @private
 * @returns {string} - Value type name
 */
export function getTypeName(value) {
  value = typeof value === 'function' ? value : value.constructor;

  return value.name;
}

/**
 * Mix traits into the target class
 * @param {Function} baseClass - Target base class for the traits to be applied to
 * @param {Function} mixins - Traits to be applied
 * @returns {Function} - Constructor with any traits applied
 * @private
 */
export function mix(baseClass, ...mixins) {
  const cocktail = class _Cocktail extends baseClass {
    constructor(...args) {
      super(...args);
      mixins.forEach(mixin => {
        if (mixin.prototype.initializer) {
          mixin.prototype.initializer.call(this);
        }
      });
    }
  };

  for (const mixin of mixins) {
    if (!isParentOf(Trait, mixin)) {
      throw new TypeError(`Expected mixin to implement Trait for ${mixin.name}`);
    }

    copyProps(cocktail.prototype, mixin.prototype);
    copyProps(cocktail, mixin);
  }

  cocktail.__mixins = mixins;

  return cocktail;
}

/**
 * Copy properties from target to source
 * @param {object} target - Object for the properties to be copied to
 * @param {object} source - Object containing properties to be copied
 * @returns {object} - Resulting object with properties from both parameters
 */
function copyProps(target, source) {
  Object
    .getOwnPropertyNames(source)
    .concat(Object.getOwnPropertySymbols(source))
    .forEach((prop) => {
      if (!prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)) {
        Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
      }
    });
}
<commit_msg>Refactor mix<commit_after>/*
 * BSD 3-Clause License
 *
 * Copyright (c) 2017, MapCreator
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 *  Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import Trait from '../traits/Trait';

/**
 * Tests if the parent is a parent of child
 * @param {function|object} parent - Instance or Class
 * @param {function|object} child - Instance or Class
 * @returns {boolean} - is parent a parent of child
 * @private
 * @example
 * class A {}
 * class B extends A {}
 * class C extends B {}
 *
 * isParentOf(A, C) // true
 * isParentOf(B, C) // true
 * isParentOf(C, C) // true
 * isParentOf(B, A) // false
 */
export function isParentOf(parent, child) {
  parent = typeof parent === 'function' ? parent : parent.constructor;
  child = typeof child === 'function' ? child : child.constructor;

  do {
    if (child.name === parent.name) {
      return true;
    }

    child = Object.getPrototypeOf(child);
  } while (child.name !== '');

  return false;
}

/**
 * Get the name of the value type
 * @param {*} value - Any value
 * @private
 * @returns {string} - Value type name
 */
export function getTypeName(value) {
  value = typeof value === 'function' ? value : value.constructor;

  return value.name;
}

/**
 * Mix traits into the target class
 * @param {Function} baseClass - Target base class for the traits to be applied to
 * @param {Function} mixins - Traits to be applied
 * @returns {Function} - Constructor with any traits applied
 * @private
 */
export function mix(baseClass, ...mixins) {
  const cocktail = class _Cocktail extends baseClass {
    constructor(...args) {
      super(...args);
      mixins
        .map(mixin => mixin.prototype.initializer)
        .filter(_ => _)
        .forEach(initializer => initializer.call(this));
    }
  };

  for (const mixin of mixins) {
    if (!isParentOf(Trait, mixin)) {
      throw new TypeError(`Expected mixin to implement Trait for ${mixin.name}`);
    }

    copyProps(cocktail.prototype, mixin.prototype);
    copyProps(cocktail, mixin);
  }

  cocktail.__mixins = mixins;

  return cocktail;
}

/**
 * Copy properties from target to source
 * @param {object} target - Object for the properties to be copied to
 * @param {object} source - Object containing properties to be copied
 * @returns {object} - Resulting object with properties from both parameters
 */
function copyProps(target, source) {
  Object
    .getOwnPropertyNames(source)
    .concat(Object.getOwnPropertySymbols(source))
    .forEach((prop) => {
      if (!prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)) {
        Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
      }
    });
}
<|endoftext|>