<commit_before>iD.Connection = function(context) {

    var event = d3.dispatch('auth', 'load'),
        url = 'http://www.openstreetmap.org',
        connection = {},
        user = {},
        keys,
        inflight = {},
        loadedTiles = {},
        oauth = iD.OAuth(context).url(url),
        ndStr = 'nd',
        tagStr = 'tag',
        memberStr = 'member',
        nodeStr = 'node',
        wayStr = 'way',
        relationStr = 'relation';

    connection.changesetUrl = function(changesetId) {
        return url + '/browse/changeset/' + changesetId;
    };

    function bboxUrl(b) {
        return url + '/api/0.6/map?bbox=' + [b[0][0],b[1][1],b[1][0],b[0][1]];
    }

    function bboxFromAPI(box, tile, callback) {
        function done(err, parsed) {
             loadedTiles[tile.toString()] = true;
             delete inflight[tile.toString()];
             callback(err, parsed);
         }
         inflight[tile.toString()] = connection.loadFromURL(bboxUrl(box), done);
    }

    connection.loadFromURL = function(url, callback) {
        function done(dom) {
            return callback(null, parse(dom));
        }
        return d3.xml(url).get().on('load', done);
    };

    function getNodes(obj) {
        var elems = obj.getElementsByTagName(ndStr),
            nodes = new Array(elems.length);
        for (var i = 0, l = elems.length; i < l; i++) {
            nodes[i] = 'n' + elems[i].attributes.ref.nodeValue;
        }
        return nodes;
    }

    function getTags(obj) {
        var elems = obj.getElementsByTagName(tagStr),
            tags = {};
        for (var i = 0, l = elems.length; i < l; i++) {
            var attrs = elems[i].attributes;
            tags[attrs.k.nodeValue] = attrs.v.nodeValue;
        }
        return tags;
    }

    function getMembers(obj) {
        var elems = obj.getElementsByTagName(memberStr),
            members = new Array(elems.length);
        for (var i = 0, l = elems.length; i < l; i++) {
            var attrs = elems[i].attributes;
            members[i] = {
                id: attrs.type.nodeValue[0] + attrs.ref.nodeValue,
                type: attrs.type.nodeValue,
                role: attrs.role.nodeValue
            };
        }
        return members;
    }

    var parsers = {
        node: function nodeData(obj) {
            var attrs = obj.attributes;
            return new iD.Node({
                id: iD.Entity.id.fromOSM(nodeStr, attrs.id.nodeValue),
                loc: [parseFloat(attrs.lon.nodeValue), parseFloat(attrs.lat.nodeValue)],
                version: attrs.version.nodeValue,
                changeset: attrs.changeset.nodeValue,
                user: attrs.user.nodeValue,
                uid: attrs.uid.nodeValue,
                visible: attrs.visible.nodeValue,
                timestamp: attrs.timestamp.nodeValue,
                tags: getTags(obj)
            });
        },

        way: function wayData(obj) {
            var attrs = obj.attributes;
            return new iD.Way({
                id: iD.Entity.id.fromOSM(wayStr, attrs.id.nodeValue),
                version: attrs.version.nodeValue,
                changeset: attrs.changeset.nodeValue,
                user: attrs.user.nodeValue,
                uid: attrs.uid.nodeValue,
                visible: attrs.visible.nodeValue,
                timestamp: attrs.timestamp.nodeValue,
                tags: getTags(obj),
                nodes: getNodes(obj)
            });
        },

        relation: function relationData(obj) {
            var attrs = obj.attributes;
            return new iD.Relation({
                id: iD.Entity.id.fromOSM(relationStr, attrs.id.nodeValue),
                version: attrs.version.nodeValue,
                changeset: attrs.changeset.nodeValue,
                user: attrs.user.nodeValue,
                uid: attrs.uid.nodeValue,
                visible: attrs.visible.nodeValue,
                timestamp: attrs.timestamp.nodeValue,
                tags: getTags(obj),
                members: getMembers(obj)
            });
        }
    };

    function parse(dom) {
        if (!dom || !dom.childNodes) return new Error('Bad request');

        var root = dom.childNodes[0],
            children = root.childNodes,
            entities = {};

        var i, o, l;
        for (i = 0, l = children.length; i < l; i++) {
            var child = children[i],
                parser = parsers[child.nodeName];
            if (parser) {
                o = parser(child);
                entities[o.id] = o;
            }
        }

        return entities;
    }

    connection.authenticated = function() {
        return oauth.authenticated();
    };

    // Generate Changeset XML. Returns a string.
    connection.changesetJXON = function(tags) {
        return {
            osm: {
                changeset: {
                    tag: _.map(tags, function(value, key) {
                        return { '@k': key, '@v': value };
                    }),
                    '@version': 0.3,
                    '@generator': 'iD'
                }
            }
        };
    };

    // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
    // XML. Returns a string.
    connection.osmChangeJXON = function(userid, changeset_id, changes) {
        function nest(x, order) {
            var groups = {};
            for (var i = 0; i < x.length; i++) {
                var tagName = Object.keys(x[i])[0];
                if (!groups[tagName]) groups[tagName] = [];
                groups[tagName].push(x[i][tagName]);
            }
            var ordered = {};
            order.forEach(function(o) {
                if (groups[o]) ordered[o] = groups[o];
            });
            return ordered;
        }

        function rep(entity) {
            return entity.asJXON(changeset_id);
        }

        return {
            osmChange: {
                '@version': 0.3,
                '@generator': 'iD',
                'create': nest(changes.created.map(rep), ['node', 'way', 'relation']),
                'modify': nest(changes.modified.map(rep), ['node', 'way', 'relation']),
                'delete': _.extend(nest(changes.deleted.map(rep), ['relation', 'way', 'node']), {'@if-unused': true})
            }
        };
    };

    connection.putChangeset = function(changes, comment, imagery_used, callback) {
        oauth.xhr({
                method: 'PUT',
                path: '/api/0.6/changeset/create',
                options: { header: { 'Content-Type': 'text/xml' } },
                content: JXON.stringify(connection.changesetJXON({
                    imagery_used: imagery_used.join(';'),
                    comment: comment,
                    created_by: 'iD ' + iD.version
                }))
            }, function(err, changeset_id) {
                if (err) return callback(err);
                oauth.xhr({
                    method: 'POST',
                    path: '/api/0.6/changeset/' + changeset_id + '/upload',
                    options: { header: { 'Content-Type': 'text/xml' } },
                    content: JXON.stringify(connection.osmChangeJXON(user.id, changeset_id, changes))
                }, function(err) {
                    if (err) return callback(err);
                    oauth.xhr({
                        method: 'PUT',
                        path: '/api/0.6/changeset/' + changeset_id + '/close'
                    }, function(err) {
                        callback(err, changeset_id);
                    });
                });
            });
    };

    connection.userDetails = function(callback) {
        function done(err, user_details) {
            if (err) return callback(err);
            var u = user_details.getElementsByTagName('user')[0],
                img = u.getElementsByTagName('img'),
                image_url = '';
            if (img && img[0].getAttribute('href')) {
                image_url = img[0].getAttribute('href');
            }
            callback(undefined, connection.user({
                display_name: u.attributes.display_name.nodeValue,
                image_url: image_url,
                id: u.attributes.id.nodeValue
            }).user());
        }
        oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done);
    };

    function tileAlreadyLoaded(c) { return !loadedTiles[c.toString()] && !inflight[c.toString()]; }

    function abortRequest(i) { i.abort(); }

    function loadTile(e) {
        function done(err, g) {
            event.load(err, g);
        }
        bboxFromAPI(e.box, e.tile, done);
    }

    connection.loadTiles = function(projection, dimensions) {
        var scaleExtent = [16, 16],
            s = projection.scale(),
            tiles = d3.geo.tile()
                .scaleExtent(scaleExtent)
                .scale(s)
                .size(dimensions)
                .translate(projection.translate())(),
            z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
            rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))),
            ts = 256 * Math.pow(2, z - rz),
            tile_origin = [
                s / 2 - projection.translate()[0],
                s / 2 - projection.translate()[1]];

        function apiExtentBox(c) {
            var x = (c[0] * ts) - tile_origin[0];
            var y = (c[1] * ts) - tile_origin[1];
            return {
                box: [
                    projection.invert([x, y]),
                    projection.invert([x + ts, y + ts])],
                tile: c
            };
        }

        _.filter(inflight, function(v, i) {
            var wanted = _.find(tiles, function(tile) {
                return i === tile.toString();
            });
            if (!wanted) delete inflight[i];
            return !wanted;
        }).map(abortRequest);

        tiles
            .filter(tileAlreadyLoaded)
            .map(apiExtentBox)
            .forEach(loadTile);
    };

    connection.userUrl = function(username) {
        return url + "/user/" + username;
    };

    connection.url = function(_) {
        if (!arguments.length) return url;
        url = _;
        oauth.url(_);
        event.auth();
        connection.flush();
        return connection;
    };

    connection.user = function(_) {
        if (!arguments.length) return user;
        user = _;
        return connection;
    };

    connection.flush = function() {
        _.forEach(inflight, abortRequest);
        loadedTiles = {};
        inflight = {};
        return connection;
    };

    connection.logout = function() {
        oauth.logout();
        event.auth();
        return connection;
    };

    connection.keys = function(_) {
        if (!arguments.length) return keys;
        keys = _;
        oauth.keys(keys);
        return connection;
    };

    connection.authenticate = function(callback) {
        function done(err, res) {
            event.auth();
            if (callback) callback(err, res);
        }
        return oauth.authenticate(done);
    };

    return d3.rebind(connection, event, 'on');
};
<commit_msg>Fix node parsing<commit_after>iD.Connection = function(context) {

    var event = d3.dispatch('auth', 'load'),
        url = 'http://www.openstreetmap.org',
        connection = {},
        user = {},
        keys,
        inflight = {},
        loadedTiles = {},
        oauth = iD.OAuth(context).url(url),
        ndStr = 'nd',
        tagStr = 'tag',
        memberStr = 'member',
        nodeStr = 'node',
        wayStr = 'way',
        relationStr = 'relation';

    connection.changesetUrl = function(changesetId) {
        return url + '/browse/changeset/' + changesetId;
    };

    function bboxUrl(b) {
        return url + '/api/0.6/map?bbox=' + [b[0][0],b[1][1],b[1][0],b[0][1]];
    }

    function bboxFromAPI(box, tile, callback) {
        function done(err, parsed) {
             loadedTiles[tile.toString()] = true;
             delete inflight[tile.toString()];
             callback(err, parsed);
         }
         inflight[tile.toString()] = connection.loadFromURL(bboxUrl(box), done);
    }

    connection.loadFromURL = function(url, callback) {
        function done(dom) {
            return callback(null, parse(dom));
        }
        return d3.xml(url).get().on('load', done);
    };

    function getNodes(obj) {
        var elems = obj.getElementsByTagName(ndStr),
            nodes = new Array(elems.length);
        for (var i = 0, l = elems.length; i < l; i++) {
            nodes[i] = 'n' + elems[i].attributes.ref.nodeValue;
        }
        return nodes;
    }

    function getTags(obj) {
        var elems = obj.getElementsByTagName(tagStr),
            tags = {};
        for (var i = 0, l = elems.length; i < l; i++) {
            var attrs = elems[i].attributes;
            tags[attrs.k.nodeValue] = attrs.v.nodeValue;
        }
        return tags;
    }

    function getMembers(obj) {
        var elems = obj.getElementsByTagName(memberStr),
            members = new Array(elems.length);
        for (var i = 0, l = elems.length; i < l; i++) {
            var attrs = elems[i].attributes;
            members[i] = {
                id: attrs.type.nodeValue[0] + attrs.ref.nodeValue,
                type: attrs.type.nodeValue,
                role: attrs.role.nodeValue
            };
        }
        return members;
    }

    var parsers = {
        node: function nodeData(obj) {
            var attrs = obj.attributes;
            return new iD.Node({
                id: iD.Entity.id.fromOSM(nodeStr, attrs.id.nodeValue),
                loc: [parseFloat(attrs.lon.nodeValue), parseFloat(attrs.lat.nodeValue)],
                version: attrs.version.nodeValue,
                changeset: attrs.changeset.nodeValue,
                user: attrs.user && attrs.user.nodeValue,
                uid: attrs.uid && attrs.uid.nodeValue,
                visible: attrs.visible.nodeValue,
                timestamp: attrs.timestamp.nodeValue,
                tags: getTags(obj)
            });
        },

        way: function wayData(obj) {
            var attrs = obj.attributes;
            return new iD.Way({
                id: iD.Entity.id.fromOSM(wayStr, attrs.id.nodeValue),
                version: attrs.version.nodeValue,
                changeset: attrs.changeset.nodeValue,
                user: attrs.user.nodeValue,
                uid: attrs.uid.nodeValue,
                visible: attrs.visible.nodeValue,
                timestamp: attrs.timestamp.nodeValue,
                tags: getTags(obj),
                nodes: getNodes(obj)
            });
        },

        relation: function relationData(obj) {
            var attrs = obj.attributes;
            return new iD.Relation({
                id: iD.Entity.id.fromOSM(relationStr, attrs.id.nodeValue),
                version: attrs.version.nodeValue,
                changeset: attrs.changeset.nodeValue,
                user: attrs.user.nodeValue,
                uid: attrs.uid.nodeValue,
                visible: attrs.visible.nodeValue,
                timestamp: attrs.timestamp.nodeValue,
                tags: getTags(obj),
                members: getMembers(obj)
            });
        }
    };

    function parse(dom) {
        if (!dom || !dom.childNodes) return new Error('Bad request');

        var root = dom.childNodes[0],
            children = root.childNodes,
            entities = {};

        var i, o, l;
        for (i = 0, l = children.length; i < l; i++) {
            var child = children[i],
                parser = parsers[child.nodeName];
            if (parser) {
                o = parser(child);
                entities[o.id] = o;
            }
        }

        return entities;
    }

    connection.authenticated = function() {
        return oauth.authenticated();
    };

    // Generate Changeset XML. Returns a string.
    connection.changesetJXON = function(tags) {
        return {
            osm: {
                changeset: {
                    tag: _.map(tags, function(value, key) {
                        return { '@k': key, '@v': value };
                    }),
                    '@version': 0.3,
                    '@generator': 'iD'
                }
            }
        };
    };

    // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
    // XML. Returns a string.
    connection.osmChangeJXON = function(userid, changeset_id, changes) {
        function nest(x, order) {
            var groups = {};
            for (var i = 0; i < x.length; i++) {
                var tagName = Object.keys(x[i])[0];
                if (!groups[tagName]) groups[tagName] = [];
                groups[tagName].push(x[i][tagName]);
            }
            var ordered = {};
            order.forEach(function(o) {
                if (groups[o]) ordered[o] = groups[o];
            });
            return ordered;
        }

        function rep(entity) {
            return entity.asJXON(changeset_id);
        }

        return {
            osmChange: {
                '@version': 0.3,
                '@generator': 'iD',
                'create': nest(changes.created.map(rep), ['node', 'way', 'relation']),
                'modify': nest(changes.modified.map(rep), ['node', 'way', 'relation']),
                'delete': _.extend(nest(changes.deleted.map(rep), ['relation', 'way', 'node']), {'@if-unused': true})
            }
        };
    };

    connection.putChangeset = function(changes, comment, imagery_used, callback) {
        oauth.xhr({
                method: 'PUT',
                path: '/api/0.6/changeset/create',
                options: { header: { 'Content-Type': 'text/xml' } },
                content: JXON.stringify(connection.changesetJXON({
                    imagery_used: imagery_used.join(';'),
                    comment: comment,
                    created_by: 'iD ' + iD.version
                }))
            }, function(err, changeset_id) {
                if (err) return callback(err);
                oauth.xhr({
                    method: 'POST',
                    path: '/api/0.6/changeset/' + changeset_id + '/upload',
                    options: { header: { 'Content-Type': 'text/xml' } },
                    content: JXON.stringify(connection.osmChangeJXON(user.id, changeset_id, changes))
                }, function(err) {
                    if (err) return callback(err);
                    oauth.xhr({
                        method: 'PUT',
                        path: '/api/0.6/changeset/' + changeset_id + '/close'
                    }, function(err) {
                        callback(err, changeset_id);
                    });
                });
            });
    };

    connection.userDetails = function(callback) {
        function done(err, user_details) {
            if (err) return callback(err);
            var u = user_details.getElementsByTagName('user')[0],
                img = u.getElementsByTagName('img'),
                image_url = '';
            if (img && img[0].getAttribute('href')) {
                image_url = img[0].getAttribute('href');
            }
            callback(undefined, connection.user({
                display_name: u.attributes.display_name.nodeValue,
                image_url: image_url,
                id: u.attributes.id.nodeValue
            }).user());
        }
        oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done);
    };

    function tileAlreadyLoaded(c) { return !loadedTiles[c.toString()] && !inflight[c.toString()]; }

    function abortRequest(i) { i.abort(); }

    function loadTile(e) {
        function done(err, g) {
            event.load(err, g);
        }
        bboxFromAPI(e.box, e.tile, done);
    }

    connection.loadTiles = function(projection, dimensions) {
        var scaleExtent = [16, 16],
            s = projection.scale(),
            tiles = d3.geo.tile()
                .scaleExtent(scaleExtent)
                .scale(s)
                .size(dimensions)
                .translate(projection.translate())(),
            z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
            rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))),
            ts = 256 * Math.pow(2, z - rz),
            tile_origin = [
                s / 2 - projection.translate()[0],
                s / 2 - projection.translate()[1]];

        function apiExtentBox(c) {
            var x = (c[0] * ts) - tile_origin[0];
            var y = (c[1] * ts) - tile_origin[1];
            return {
                box: [
                    projection.invert([x, y]),
                    projection.invert([x + ts, y + ts])],
                tile: c
            };
        }

        _.filter(inflight, function(v, i) {
            var wanted = _.find(tiles, function(tile) {
                return i === tile.toString();
            });
            if (!wanted) delete inflight[i];
            return !wanted;
        }).map(abortRequest);

        tiles
            .filter(tileAlreadyLoaded)
            .map(apiExtentBox)
            .forEach(loadTile);
    };

    connection.userUrl = function(username) {
        return url + "/user/" + username;
    };

    connection.url = function(_) {
        if (!arguments.length) return url;
        url = _;
        oauth.url(_);
        event.auth();
        connection.flush();
        return connection;
    };

    connection.user = function(_) {
        if (!arguments.length) return user;
        user = _;
        return connection;
    };

    connection.flush = function() {
        _.forEach(inflight, abortRequest);
        loadedTiles = {};
        inflight = {};
        return connection;
    };

    connection.logout = function() {
        oauth.logout();
        event.auth();
        return connection;
    };

    connection.keys = function(_) {
        if (!arguments.length) return keys;
        keys = _;
        oauth.keys(keys);
        return connection;
    };

    connection.authenticate = function(callback) {
        function done(err, res) {
            event.auth();
            if (callback) callback(err, res);
        }
        return oauth.authenticate(done);
    };

    return d3.rebind(connection, event, 'on');
};
<|endoftext|>


<commit_before>'use strict'
var traceHelper = require('../helpers/traceHelper')
var util = require('../helpers/global')

class StorageResolver {
  constructor () {
    this.storageByAddress = {}
    this.maxSize = 100
  }

  /**
    * returns the storage for the given context (address and vm trace index)
    * returns the range 0x0 => this.maxSize
    *
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Object} - storageChanges - contains storage changes by hashde key
    * @param {String} - address - lookup address
    * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value}
    */
  storageRange (tx, stepIndex, storageChanges, address, callback) {
    storageRangeInternal(this, zeroSlot, tx, stepIndex, true, storageChanges, address, callback)
  }

  /**
    * return a slot value for the given context (address and vm trace index)
    *
    * @param {String} - slot - slot key
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Object} - storageChanges - contains storage changes by hashde key
    * @param {String} - address - lookup address
    * @param {Function} - callback - {key, hashedKey, value} -
    */
  storageSlot (slot, tx, stepIndex, storageChanges, address, callback) {
    storageRangeInternal(this, slot, tx, stepIndex, false, storageChanges, address, function (error, storage) {
      if (error) {
        callback(error)
      } else {
        callback(null, storage[slot] !== undefined ? storage[slot] : null)
      }
    })
  }

  /**
    * return True if the storage at @arg address is complete
    *
    * @param {String} address  - contract address
    * @return {Bool} - return True if the storage at @arg address is complete
    */
  isComplete (address) {
    return this.storageByAddress[address] && this.storageByAddress[address].complete
  }
}

/**
  * retrieve the storage and ensure at least @arg slot is cached.
  * - If @arg slot is already cached, the storage will be returned from the cache
  *   even if the next 1000 items are not in the cache.
  * - If @arg slot is not cached, the corresponding value will be resolved and the next 1000 slots.
  */
function storageRangeInternal (self, slotKey, tx, stepIndex, fullStorage, storageChanges, address, callback) {
  if (!fullStorage && storageChanges[slotKey]) { // don't need the full storage just returning the value from the storageChanges
    return callback(null, storageChanges)
  }
  var cached = fromCache(self, address)
  if (cached && cached.storage[slotKey]) { // we have the current slot in the cache and maybe the next 1000...
    return callback(null, Object.assign({}, cached.storage, storageChanges))
  }
  storageRangeWeb3Call(tx, address, slotKey, self.maxSize, (error, storage, complete) => {
    if (error) {
      return callback(error)
    }
    if (!storage[slotKey]) {
      storage[slotKey] = {
        key: slotKey,
        value: zeroSlot
      }
    }
    toCache(self, address, storage)
    if (slotKey === zeroSlot && Object.keys(storage).length < self.maxSize) {
      self.storageByAddress[address].complete = true
    }
    callback(null, Object.assign(storage, storageChanges))
  })
}

var zeroSlot = '0x0000000000000000000000000000000000000000000000000000000000000000'

/**
  * retrieve the storage from the cache. if @arg slot is defined, return only the desired slot, if not return the entire known storage
  *
  * @param {String} address  - contract address
  * @return {String} - either the entire known storage or a single value
  */
function fromCache (self, address) {
  if (!self.storageByAddress[address]) {
    return null
  }
  return self.storageByAddress[address]
}

/**
 * store the result of `storageRangeAtInternal`
 *
 * @param {String} address  - contract address
 * @param {Object} storage  - result of `storageRangeAtInternal`, contains {key, hashedKey, value}
 */
function toCache (self, address, storage) {
  if (!self.storageByAddress[address]) {
    self.storageByAddress[address] = {}
  }
  self.storageByAddress[address].storage = Object.assign(self.storageByAddress[address].storage || {}, storage)
}

function storageRangeWeb3Call (tx, address, start, maxSize, callback) {
  if (traceHelper.isContractCreation(address)) {
    callback(null, {}, true)
  } else {
    util.web3.debug.storageRangeAt(
      tx.blockHash, tx.transactionIndex === undefined ? tx.hash : tx.transactionIndex,
      address,
      start,
      maxSize,
      (error, result) => {
        if (error) {
          callback(error)
        } else if (result.storage) {
          callback(null, result.storage, result.complete)
        } else {
          callback('the storage has not been provided')
        }
      })
  }
}

module.exports = StorageResolver
<commit_msg>warning (keys should be sorted)<commit_after>'use strict'
var traceHelper = require('../helpers/traceHelper')
var util = require('../helpers/global')

class StorageResolver {
  constructor () {
    this.storageByAddress = {}
    this.maxSize = 100
  }

  /**
    * returns the storage for the given context (address and vm trace index)
    * returns the range 0x0 => this.maxSize
    *
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Object} - storageChanges - contains storage changes by hashde key
    * @param {String} - address - lookup address
    * @param {Function} - callback - contains a map: [hashedKey] = {key, hashedKey, value}
    */
  storageRange (tx, stepIndex, storageChanges, address, callback) {
    storageRangeInternal(this, zeroSlot, tx, stepIndex, true, storageChanges, address, callback)
  }

  /**
    * return a slot value for the given context (address and vm trace index)
    *
    * @param {String} - slot - slot key
    * @param {Object} - tx - transaction
    * @param {Int} - stepIndex - Index of the stop in the vm trace
    * @param {Object} - storageChanges - contains storage changes by hashde key
    * @param {String} - address - lookup address
    * @param {Function} - callback - {key, hashedKey, value} -
    */
  storageSlot (slot, tx, stepIndex, storageChanges, address, callback) {
    storageRangeInternal(this, slot, tx, stepIndex, false, storageChanges, address, function (error, storage) {
      if (error) {
        callback(error)
      } else {
        callback(null, storage[slot] !== undefined ? storage[slot] : null)
      }
    })
  }

  /**
    * return True if the storage at @arg address is complete
    *
    * @param {String} address  - contract address
    * @return {Bool} - return True if the storage at @arg address is complete
    */
  isComplete (address) {
    return this.storageByAddress[address] && this.storageByAddress[address].complete
  }
}

/**
  * retrieve the storage and ensure at least @arg slot is cached.
  * - If @arg slot is already cached, the storage will be returned from the cache
  *   even if the next 1000 items are not in the cache.
  * - If @arg slot is not cached, the corresponding value will be resolved and the next 1000 slots.
  */
function storageRangeInternal (self, slotKey, tx, stepIndex, fullStorage, storageChanges, address, callback) {
  if (!fullStorage && storageChanges[slotKey]) { // don't need the full storage just returning the value from the storageChanges
    return callback(null, storageChanges)
  }
  var cached = fromCache(self, address)
  if (cached && cached.storage[slotKey]) { // we have the current slot in the cache and maybe the next 1000...
    return callback(null, Object.assign({}, cached.storage, storageChanges))
  }
  storageRangeWeb3Call(tx, address, slotKey, self.maxSize, (error, storage, complete) => {
    if (error) {
      return callback(error)
    }
    if (!storage[slotKey]) {
      storage[slotKey] = {
        key: slotKey,
        value: zeroSlot
      }
    }
    toCache(self, address, storage)
    if (slotKey === zeroSlot && Object.keys(storage).length < self.maxSize) { // only working if keys are sorted !!
      self.storageByAddress[address].complete = true
    }
    callback(null, Object.assign(storage, storageChanges))
  })
}

var zeroSlot = '0x0000000000000000000000000000000000000000000000000000000000000000'

/**
  * retrieve the storage from the cache. if @arg slot is defined, return only the desired slot, if not return the entire known storage
  *
  * @param {String} address  - contract address
  * @return {String} - either the entire known storage or a single value
  */
function fromCache (self, address) {
  if (!self.storageByAddress[address]) {
    return null
  }
  return self.storageByAddress[address]
}

/**
 * store the result of `storageRangeAtInternal`
 *
 * @param {String} address  - contract address
 * @param {Object} storage  - result of `storageRangeAtInternal`, contains {key, hashedKey, value}
 */
function toCache (self, address, storage) {
  if (!self.storageByAddress[address]) {
    self.storageByAddress[address] = {}
  }
  self.storageByAddress[address].storage = Object.assign(self.storageByAddress[address].storage || {}, storage)
}

function storageRangeWeb3Call (tx, address, start, maxSize, callback) {
  if (traceHelper.isContractCreation(address)) {
    callback(null, {}, true)
  } else {
    util.web3.debug.storageRangeAt(
      tx.blockHash, tx.transactionIndex === undefined ? tx.hash : tx.transactionIndex,
      address,
      start,
      maxSize,
      (error, result) => {
        if (error) {
          callback(error)
        } else if (result.storage) {
          callback(null, result.storage, result.complete)
        } else {
          callback('the storage has not been provided')
        }
      })
  }
}

module.exports = StorageResolver
<|endoftext|>


<commit_before>/*!
 * jQuery Mobile
 * http://jquerymobile.com/
 *
 * Copyright 2010, jQuery Project
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */
(function( jQuery, window, undefined ) {
	//some critical feature tests should be placed here.
	//if we're missing support for any of these, then we're a C-grade browser
	//to-do: see if we need more qualifiers here.
	if ( !jQuery.support.mediaquery ) {
		return;
	}

	//these properties should be made easy to override externally
	jQuery.mobile = {};

	jQuery.extend(jQuery.mobile, {
		subPageUrlKey: 'ui-page', //define the key used in urls for sub-pages. Defaults to &ui-page=
		degradeInputs: {
			color: true,
			date: true,
			datetime: true,
			"datetime-local": true,
			email: true,
			month: true,
			number: true,
			range: true,
			search: true,
			tel: true,
			time: true,
			url: true,
			week: true
		},
		addBackBtn: true
	});

	var $window = jQuery(window),
		$html = jQuery('html'),
		$head = jQuery('head'),
		$body,
		$loader = jQuery('

loading

'),
		$startPage,
		$pageContainer,
		startPageId = 'ui-page-start',
		activePageClass = 'ui-page-active',
		pageTransition,
		forceBack,
		transitions = 'slide slideup slidedown pop flip fade',
		transitionDuration = 350,
		backBtnText = "Back",
		urlStack = [ {
			url: location.hash.replace( /^#/, "" ),
			transition: "slide"
		} ],
		focusable = "[tabindex],a,button:visible,select:visible,input",
		nextPageRole = null;

	// TODO: don't expose (temporary during code reorg)
	$.mobile.urlStack = urlStack;

	//consistent string escaping for urls and IDs
	function idStringEscape(str){
		return str.replace(/[^a-zA-Z0-9]/g, '-');
	}

	$.mobile.idStringEscape = idStringEscape;

	// hide address bar
	function hideBrowserChrome() {
		// prevent scrollstart and scrollstop events
		jQuery.event.special.scrollstart.enabled = false;
		setTimeout(function() {
			window.scrollTo( 0, 0 );
		},0);
		setTimeout(function() {
			jQuery.event.special.scrollstart.enabled = true;
		}, 150 );
	}

	function getBaseURL(){
	    var newBaseURL = location.hash.replace(/#/,'').split('/');
		if(newBaseURL.length && /[.|&]/.test(newBaseURL[newBaseURL.length-1]) ){
			newBaseURL.pop();
		}
		newBaseURL = newBaseURL.join('/');
		if(newBaseURL !== "" && newBaseURL.charAt(newBaseURL.length-1) !== '/'){  newBaseURL += '/'; }
		return newBaseURL;
	}

	function setBaseURL(){
		//set base url for new page assets
		$('#ui-base').attr('href', getBaseURL());
	}

	function resetBaseURL(){
		$('#ui-base').attr('href', location.pathname);
	}

	// send a link through hash tracking
	jQuery.fn.ajaxClick = function() {
		var href = jQuery( this ).attr( "href" );
		pageTransition = jQuery( this ).data( "transition" ) || "slide";
		forceBack = jQuery( this ).data( "back" ) || undefined;
		nextPageRole = jQuery( this ).attr( "data-rel" );

		//find new base for url building
		var newBaseURL = getBaseURL();

		//if href is absolute but local, or a local ID, no base needed
		if( /^\//.test(href) || (/https?:\/\//.test(href) && !!(href).match(location.hostname)) || /^#/.test(href) ){
			newBaseURL = '';
		}

		// set href to relative path using baseURL and
		if( !/https?:\/\//.test(href) ){
			href = newBaseURL + href;
		}

		//if it's a non-local-anchor and Ajax is not supported, or if it's an external link, go to page without ajax
		if ( ( /^[^#]/.test(href) && !jQuery.support.ajax ) || ( /https?:\/\//.test(href) && !!!href.match(location.hostname) ) ) {
			location = href
		}
		else{
			// let the hashchange event handler take care of requesting the page via ajax
			location.hash = href;
		}
		return this;
	};

	// ajaxify all navigable links
	jQuery( "a:not([href=#]):not([target]):not([rel=external]):not([href^=mailto:])" ).live( "click", function(event) {
		jQuery( this ).ajaxClick();
		return false;
	});

	// turn on/off page loading message.
	function pageLoading( done ) {
		if ( done ) {
			$html.removeClass( "ui-loading" );
		} else {
			$loader.appendTo($pageContainer).css({top: $(window).scrollTop() + 75});
			$html.addClass( "ui-loading" );
		}
	};

	//for directing focus to the page title, or otherwise first focusable element
	function reFocus(page){
		var pageTitle = page.find( ".ui-title:eq(0)" );
		if( pageTitle.length ){
			pageTitle.focus();
		}
		else{
			page.find( focusable ).eq(0).focus();
		}
	}

	// transition between pages - based on transitions from jQtouch
	function changePage( from, to, transition, back ) {
		jQuery( document.activeElement ).blur();

		//trigger before show/hide events
		from.trigger("beforepagehide", {nextPage: to});
		to.trigger("beforepageshow", {prevPage: from});

		function loadComplete(){
			pageLoading( true );
			//trigger show/hide events, allow preventing focus change through return false
			if( from.trigger("pagehide", {nextPage: to}) !== false && to.trigger("pageshow", {prevPage: from}) !== false ){
				reFocus( to );
			}
		}

		if(transition){
			// animate in / out
			from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
			to.addClass( activePageClass + " " + transition +
				" in " + ( back ? "reverse" : "" ) );

			// callback - remove classes, etc
			to.animationComplete(function() {
				from.add( to ).removeClass(" out in reverse " + transitions );
				from.removeClass( activePageClass );
				loadComplete();
			});
		}
		else{
			from.removeClass( activePageClass );
			to.addClass( activePageClass );
			loadComplete();
		}
	};

	jQuery(function() {
		var preventLoad = false;

		$body = jQuery( "body" );
		pageLoading();

		// needs to be bound at domready (for IE6)
		// find or load content, make it active
		$window.bind( "hashchange", function(e, extras) {
			if ( preventLoad ) {
				preventLoad = false;
				return;
			}

			var url = location.hash.replace( /^#/, "" ),
				stackLength = urlStack.length,
				// pageTransition only exists if the user clicked a link
				back = !pageTransition && stackLength > 1 &&
					urlStack[ stackLength - 2 ].url === url,
				transition = (extras && extras.manuallyTriggered) ? false : pageTransition || "slide",
				fileUrl = url;
			pageTransition = undefined;

			//reset base to pathname for new request
			resetBaseURL();

			// if the new href is the same as the previous one
			if ( back ) {
				transition = urlStack.pop().transition;
			} else {
				urlStack.push({ url: url, transition: transition });
				if ( forceBack ) back = true;
			}
			forceBack = undefined;

			//function for setting role of next page
			function setPageRole( newPage ) {
				if ( nextPageRole ) {
					newPage.attr( "data-role", nextPageRole );
					nextPageRole = undefined;
				}
			}

			//wrap page and transfer data-attrs if it has an ID
			function wrapNewPage( newPage ){
				var copyAttrs = ['data-role', 'data-theme', 'data-fullscreen'], //TODO: more page-level attrs?
					wrapper = newPage.wrap( "
" ).parent();

				$.each(copyAttrs,function(i){
					if( newPage.attr( copyAttrs[ i ] ) ){
						wrapper.attr( copyAttrs[ i ], newPage.attr( copyAttrs[ i ] ) );
						newPage.removeAttr( copyAttrs[ i ] );
					}
				});
				return wrapper;
			}

			if ( url ) {
				var active = jQuery( ".ui-page-active" );

				// see if content is present already
				var localDiv = jQuery( "[id='" + url + "']" );
				if ( localDiv.length ) {
					if ( localDiv.is( "[data-role]" ) ) {
						setPageRole( localDiv );
					}
					setBaseURL();
					localDiv.page();
					changePage( active, localDiv, transition, back );

				} else { //ajax it in
					pageLoading();

					if ( url.match( '&' + jQuery.mobile.subPageUrlKey ) ) {
						fileUrl = url.split( '&' + jQuery.mobile.subPageUrlKey )[0];
					}

					$.ajax({
						url: fileUrl,
						success: function( html ) {
							var page = jQuery("
" + html + "
").find('[data-role="page"]');

							if ( page.attr('id') ) {
								page = wrapNewPage( page );
							}

							page
								.attr( "id", fileUrl )
								.appendTo( $pageContainer );

							setPageRole( page );
							page.page();
							changePage( active, page, transition, back );
						},
						error: function() {
							pageLoading( true );

							jQuery("

Error Loading Page

")
								.css({ "display": "block", "opacity": 0.96 })
								.appendTo( $pageContainer )
								.delay( 800 )
								.fadeOut( 400, function(){
									$(this).remove();
								});

							preventLoad = true;
							history.back();
						}
					});

					setBaseURL();
				}
			} else {
				// either we've backed up to the root page url
				// or it's the first page load with no hash present
				var currentPage = jQuery( ".ui-page-active" );
				if ( currentPage.length && !$startPage.is( ".ui-page-active" ) ) {
					changePage( currentPage, $startPage, transition, back );
				} else {
					$startPage.trigger("beforepageshow", {prevPage: $('')});
					$startPage.addClass( activePageClass );
					pageLoading( true );

					if( $startPage.trigger("pageshow", {prevPage: $('')}) !== false ){
						reFocus($startPage);
					}
				}
			}
		});
	});

	//add orientation class on flip/resize.
	$window.bind( "orientationchange", function( event, data ) {
		$html.removeClass( "portrait landscape" ).addClass( data.orientation );
	});

	//add mobile, loading classes to doc
	$html.addClass('ui-mobile');

	//insert mobile meta - these will need to be configurable somehow.
	$head.prepend(
		'<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />' +
		'<base  href="" id="ui-base" />'
	);

    //set base href to pathname
    resetBaseURL();

	//incomplete fallback to workaround lack of animation callbacks.
	//should this be extended into a full special event?
	// note: Expects CSS animations use transitionDuration (350ms)
	jQuery.fn.animationComplete = function(callback){
		if(jQuery.support.WebKitAnimationEvent){
			return jQuery(this).one('webkitAnimationEnd', callback); //check out transitionEnd (opera per Paul's request)
		}
		else{
			setTimeout(callback, transitionDuration);
		}
	};

	jQuery.extend({
		pageLoading: pageLoading,
		changePage: changePage,
		hideBrowserChrome: hideBrowserChrome
	});

	//dom-ready
	jQuery(function(){

		//set up active page
		$startPage = jQuery('[data-role="page"]:first');

		//set page container
		$pageContainer = $startPage.parent();

		//make sure it has an ID - for finding it later
		if(!$startPage.attr('id')){
			$startPage.attr('id', $startPageId);
		}

		//initialize all pages present
		jQuery('[data-role="page"]').page();

		//trigger a new hashchange, hash or not
		$window.trigger( "hashchange", { manuallyTriggered: true } );

		//update orientation
		$html.addClass( jQuery.event.special.orientationchange.orientation( $window ) );
	});

	$window.load(hideBrowserChrome);

})( jQuery, this );
<commit_msg>exposed pageContainer via $.pageContainer, for other plugins to append<commit_after>/*!
 * jQuery Mobile
 * http://jquerymobile.com/
 *
 * Copyright 2010, jQuery Project
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 */
(function( jQuery, window, undefined ) {
	//some critical feature tests should be placed here.
	//if we're missing support for any of these, then we're a C-grade browser
	//to-do: see if we need more qualifiers here.
	if ( !jQuery.support.mediaquery ) {
		return;
	}

	//these properties should be made easy to override externally
	jQuery.mobile = {};

	jQuery.extend(jQuery.mobile, {
		subPageUrlKey: 'ui-page', //define the key used in urls for sub-pages. Defaults to &ui-page=
		degradeInputs: {
			color: true,
			date: true,
			datetime: true,
			"datetime-local": true,
			email: true,
			month: true,
			number: true,
			range: true,
			search: true,
			tel: true,
			time: true,
			url: true,
			week: true
		},
		addBackBtn: true
	});

	var $window = jQuery(window),
		$html = jQuery('html'),
		$head = jQuery('head'),
		$body,
		$loader = jQuery('

loading

'),
		$startPage,
		$pageContainer,
		startPageId = 'ui-page-start',
		activePageClass = 'ui-page-active',
		pageTransition,
		forceBack,
		transitions = 'slide slideup slidedown pop flip fade',
		transitionDuration = 350,
		backBtnText = "Back",
		urlStack = [ {
			url: location.hash.replace( /^#/, "" ),
			transition: "slide"
		} ],
		focusable = "[tabindex],a,button:visible,select:visible,input",
		nextPageRole = null;

	// TODO: don't expose (temporary during code reorg)
	$.mobile.urlStack = urlStack;

	//consistent string escaping for urls and IDs
	function idStringEscape(str){
		return str.replace(/[^a-zA-Z0-9]/g, '-');
	}

	$.mobile.idStringEscape = idStringEscape;

	// hide address bar
	function hideBrowserChrome() {
		// prevent scrollstart and scrollstop events
		jQuery.event.special.scrollstart.enabled = false;
		setTimeout(function() {
			window.scrollTo( 0, 0 );
		},0);
		setTimeout(function() {
			jQuery.event.special.scrollstart.enabled = true;
		}, 150 );
	}

	function getBaseURL(){
	    var newBaseURL = location.hash.replace(/#/,'').split('/');
		if(newBaseURL.length && /[.|&]/.test(newBaseURL[newBaseURL.length-1]) ){
			newBaseURL.pop();
		}
		newBaseURL = newBaseURL.join('/');
		if(newBaseURL !== "" && newBaseURL.charAt(newBaseURL.length-1) !== '/'){  newBaseURL += '/'; }
		return newBaseURL;
	}

	function setBaseURL(){
		//set base url for new page assets
		$('#ui-base').attr('href', getBaseURL());
	}

	function resetBaseURL(){
		$('#ui-base').attr('href', location.pathname);
	}

	// send a link through hash tracking
	jQuery.fn.ajaxClick = function() {
		var href = jQuery( this ).attr( "href" );
		pageTransition = jQuery( this ).data( "transition" ) || "slide";
		forceBack = jQuery( this ).data( "back" ) || undefined;
		nextPageRole = jQuery( this ).attr( "data-rel" );

		//find new base for url building
		var newBaseURL = getBaseURL();

		//if href is absolute but local, or a local ID, no base needed
		if( /^\//.test(href) || (/https?:\/\//.test(href) && !!(href).match(location.hostname)) || /^#/.test(href) ){
			newBaseURL = '';
		}

		// set href to relative path using baseURL and
		if( !/https?:\/\//.test(href) ){
			href = newBaseURL + href;
		}

		//if it's a non-local-anchor and Ajax is not supported, or if it's an external link, go to page without ajax
		if ( ( /^[^#]/.test(href) && !jQuery.support.ajax ) || ( /https?:\/\//.test(href) && !!!href.match(location.hostname) ) ) {
			location = href
		}
		else{
			// let the hashchange event handler take care of requesting the page via ajax
			location.hash = href;
		}
		return this;
	};

	// ajaxify all navigable links
	jQuery( "a:not([href=#]):not([target]):not([rel=external]):not([href^=mailto:])" ).live( "click", function(event) {
		jQuery( this ).ajaxClick();
		return false;
	});

	// turn on/off page loading message.
	function pageLoading( done ) {
		if ( done ) {
			$html.removeClass( "ui-loading" );
		} else {
			$loader.appendTo($pageContainer).css({top: $(window).scrollTop() + 75});
			$html.addClass( "ui-loading" );
		}
	};

	//for directing focus to the page title, or otherwise first focusable element
	function reFocus(page){
		var pageTitle = page.find( ".ui-title:eq(0)" );
		if( pageTitle.length ){
			pageTitle.focus();
		}
		else{
			page.find( focusable ).eq(0).focus();
		}
	}

	// transition between pages - based on transitions from jQtouch
	function changePage( from, to, transition, back ) {
		jQuery( document.activeElement ).blur();

		//trigger before show/hide events
		from.trigger("beforepagehide", {nextPage: to});
		to.trigger("beforepageshow", {prevPage: from});

		function loadComplete(){
			pageLoading( true );
			//trigger show/hide events, allow preventing focus change through return false
			if( from.trigger("pagehide", {nextPage: to}) !== false && to.trigger("pageshow", {prevPage: from}) !== false ){
				reFocus( to );
			}
		}

		if(transition){
			// animate in / out
			from.addClass( transition + " out " + ( back ? "reverse" : "" ) );
			to.addClass( activePageClass + " " + transition +
				" in " + ( back ? "reverse" : "" ) );

			// callback - remove classes, etc
			to.animationComplete(function() {
				from.add( to ).removeClass(" out in reverse " + transitions );
				from.removeClass( activePageClass );
				loadComplete();
			});
		}
		else{
			from.removeClass( activePageClass );
			to.addClass( activePageClass );
			loadComplete();
		}
	};

	jQuery(function() {
		var preventLoad = false;

		$body = jQuery( "body" );
		pageLoading();

		// needs to be bound at domready (for IE6)
		// find or load content, make it active
		$window.bind( "hashchange", function(e, extras) {
			if ( preventLoad ) {
				preventLoad = false;
				return;
			}

			var url = location.hash.replace( /^#/, "" ),
				stackLength = urlStack.length,
				// pageTransition only exists if the user clicked a link
				back = !pageTransition && stackLength > 1 &&
					urlStack[ stackLength - 2 ].url === url,
				transition = (extras && extras.manuallyTriggered) ? false : pageTransition || "slide",
				fileUrl = url;
			pageTransition = undefined;

			//reset base to pathname for new request
			resetBaseURL();

			// if the new href is the same as the previous one
			if ( back ) {
				transition = urlStack.pop().transition;
			} else {
				urlStack.push({ url: url, transition: transition });
				if ( forceBack ) back = true;
			}
			forceBack = undefined;

			//function for setting role of next page
			function setPageRole( newPage ) {
				if ( nextPageRole ) {
					newPage.attr( "data-role", nextPageRole );
					nextPageRole = undefined;
				}
			}

			//wrap page and transfer data-attrs if it has an ID
			function wrapNewPage( newPage ){
				var copyAttrs = ['data-role', 'data-theme', 'data-fullscreen'], //TODO: more page-level attrs?
					wrapper = newPage.wrap( "
" ).parent();

				$.each(copyAttrs,function(i){
					if( newPage.attr( copyAttrs[ i ] ) ){
						wrapper.attr( copyAttrs[ i ], newPage.attr( copyAttrs[ i ] ) );
						newPage.removeAttr( copyAttrs[ i ] );
					}
				});
				return wrapper;
			}

			if ( url ) {
				var active = jQuery( ".ui-page-active" );

				// see if content is present already
				var localDiv = jQuery( "[id='" + url + "']" );
				if ( localDiv.length ) {
					if ( localDiv.is( "[data-role]" ) ) {
						setPageRole( localDiv );
					}
					setBaseURL();
					localDiv.page();
					changePage( active, localDiv, transition, back );

				} else { //ajax it in
					pageLoading();

					if ( url.match( '&' + jQuery.mobile.subPageUrlKey ) ) {
						fileUrl = url.split( '&' + jQuery.mobile.subPageUrlKey )[0];
					}

					$.ajax({
						url: fileUrl,
						success: function( html ) {
							var page = jQuery("
" + html + "
").find('[data-role="page"]');

							if ( page.attr('id') ) {
								page = wrapNewPage( page );
							}

							page
								.attr( "id", fileUrl )
								.appendTo( $pageContainer );

							setPageRole( page );
							page.page();
							changePage( active, page, transition, back );
						},
						error: function() {
							pageLoading( true );

							jQuery("

Error Loading Page

")
								.css({ "display": "block", "opacity": 0.96 })
								.appendTo( $pageContainer )
								.delay( 800 )
								.fadeOut( 400, function(){
									$(this).remove();
								});

							preventLoad = true;
							history.back();
						}
					});

					setBaseURL();
				}
			} else {
				// either we've backed up to the root page url
				// or it's the first page load with no hash present
				var currentPage = jQuery( ".ui-page-active" );
				if ( currentPage.length && !$startPage.is( ".ui-page-active" ) ) {
					changePage( currentPage, $startPage, transition, back );
				} else {
					$startPage.trigger("beforepageshow", {prevPage: $('')});
					$startPage.addClass( activePageClass );
					pageLoading( true );

					if( $startPage.trigger("pageshow", {prevPage: $('')}) !== false ){
						reFocus($startPage);
					}
				}
			}
		});
	});

	//add orientation class on flip/resize.
	$window.bind( "orientationchange", function( event, data ) {
		$html.removeClass( "portrait landscape" ).addClass( data.orientation );
	});

	//add mobile, loading classes to doc
	$html.addClass('ui-mobile');

	//insert mobile meta - these will need to be configurable somehow.
	$head.prepend(
		'<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />' +
		'<base  href="" id="ui-base" />'
	);

    //set base href to pathname
    resetBaseURL();

	//incomplete fallback to workaround lack of animation callbacks.
	//should this be extended into a full special event?
	// note: Expects CSS animations use transitionDuration (350ms)
	jQuery.fn.animationComplete = function(callback){
		if(jQuery.support.WebKitAnimationEvent){
			return jQuery(this).one('webkitAnimationEnd', callback); //check out transitionEnd (opera per Paul's request)
		}
		else{
			setTimeout(callback, transitionDuration);
		}
	};

	jQuery.extend({
		pageLoading: pageLoading,
		changePage: changePage,
		hideBrowserChrome: hideBrowserChrome,
		pageContainer: $pageContainer
	});

	//dom-ready
	jQuery(function(){

		//set up active page
		$startPage = jQuery('[data-role="page"]:first');

		//set page container
		$pageContainer = $startPage.parent();

		//make sure it has an ID - for finding it later
		if(!$startPage.attr('id')){
			$startPage.attr('id', $startPageId);
		}

		//initialize all pages present
		jQuery('[data-role="page"]').page();

		//trigger a new hashchange, hash or not
		$window.trigger( "hashchange", { manuallyTriggered: true } );

		//update orientation
		$html.addClass( jQuery.event.special.orientationchange.orientation( $window ) );
	});

	$window.load(hideBrowserChrome);

})( jQuery, this );
<|endoftext|>


<commit_before>import moment from 'moment'

import { makePayment } from '../../api/payments'
import merge from '../../util/merge'
import { loadMoreTransactions } from './transaction'
import { UNEXPECTED_ERROR, UNAUTHORIZED_ACCESS } from '../../api/apiError'
import { logout } from './login'

const initialState = {
  payeeId: '',
  amount: '',
  amountPaid: '',
  description: '',
  loading: false,
  success: undefined,
  message: '',
  timestamp: undefined,
  inputPage: 0,
  transactionNumber: -1,
  resetClipboard: false,
  alertShouldPopUp: false
}

const Page = {
  Ready: 0,
  EnterAmount: 1,
  ConfirmAmount: 2,
  MakingPayment: 3,
  PaymentComplete: 4
}

export const resetForm = () => ({
  type: 'sendMoney/RESET_FORM'
})

export const updatePayee = (payeeId) => ({
  type: 'sendMoney/UPDATE_PAYEE',
  payeeId
})

export const updatePage = (page) => ({
  type: 'sendMoney/UPDATE_PAGE',
  page
})

export const updateAmount = (amount) => ({
  type: 'sendMoney/UPDATE_AMOUNT',
  amount
})

export const updateDescription = (description) => ({
  type: 'sendMoney/UPDATE_DESCRIPTION',
  description
})

export const returnToPayment = () => ({
  type: 'sendMoney/RETURN_TO_PAYMENT'
})

export const resetPayment = () => ({
  type: 'sendMoney/RESET_PAYMENT'
})

const setLoading = () => ({
  type: 'sendMoney/SET_LOADING'
})

const transactionComplete = (success, message, amountPaid, timestamp, transactionNumber) => ({
  type: 'sendMoney/TRANSACTION_COMPLETE',
  success,
  message,
  amountPaid,
  timestamp,
  transactionNumber
})

export const askToContinuePayment = (value) => ({
  type: 'sendMoney/ASK_CONTINUE_PAYMENT',
  value
})

export const sendTransaction = () =>
  (dispatch, getState) => {
    if (getState().sendMoney.loading) {
      return
    }
    dispatch(setLoading())
    const { payeeId, amount, description } = getState().sendMoney
    makePayment({
        subject: payeeId,
        description: description,
        amount: amount
      }, dispatch)
      .then((result) => {
        dispatch(loadMoreTransactions())
        dispatch(transactionComplete(true, 'Transaction complete', amount, moment(result.date).format('MMMM Do YYYY, h:mm:ss a'), result.transactionNumber))
      })
      .catch(err => {
        if (err.type === UNAUTHORIZED_ACCESS) {
            dispatch(transactionComplete(false, 'Session expired', 0, null, null))
            dispatch(logout())
            dispatch(askToContinuePayment(true))
        } else if (err.type === UNEXPECTED_ERROR) {
          err.response.json()
            .then(json => {
              if (json && json.code === 'dailyAmountExceeded') {
                dispatch(transactionComplete(false, 'Daily amount has been exceeded.', 0, null, null))
              } else if (json && json.code === 'insufficientBalance') {
                dispatch(transactionComplete(false, 'Payment failed.', 0, null, null))
              } else {
                dispatch(transactionComplete(false, 'Error on sending transaction.', 0, null, null))
              }
            })
            .catch(() => dispatch(transactionComplete(false, 'Error on sending transaction.', 0, null, null)))
        } else {
          dispatch(transactionComplete(false, 'Error on sending transaction.', 0, null, null))
        }
      })
  }

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'sendMoney/RESET_FORM':
      state = initialState
      break
    case 'sendMoney/RESET_PAYMENT':
      state = merge(state, {
        amount: '',
        amountPaid: '',
        loading: false,
        success: undefined,
        message: '',
        timestamp: undefined,
        inputPage: 0,
        transactionNumber: -1,
        resetClipboard: false,
        alertShouldPopUp: false
      })
      break
    case 'sendMoney/UPDATE_PAYEE':
      state = merge(state, {
        payeeId: action.payeeId
      })
      break
    case 'sendMoney/UPDATE_AMOUNT':
      state = merge(state, {
        amount: action.amount
      })
      break
    case 'sendMoney/UPDATE_DESCRIPTION':
      state = merge(state, {
        description: action.description
      })
      break
    case 'sendMoney/SET_LOADING':
      state = merge(state, {
        loading: true
      })
      break
    case 'sendMoney/UPDATE_PAGE':
      state = merge(state, {
        inputPage: action.page,
        resetClipboard: false
      })
      break
    case 'sendMoney/TRANSACTION_COMPLETE':
      var stateToUpdate = {
        success: action.success,
        message: action.message,
        amountPaid: action.amountPaid,
        timestamp: action.timestamp,
        loading: false,
        transactionNumber: action.transactionNumber
      }
      if (action.message !== 'Session expired') {
        stateToUpdate.amount = ''
        stateToUpdate.inputPage = Page.PaymentComplete
      }
      state = merge(state, stateToUpdate)
      break
    case 'sendMoney/RETURN_TO_PAYMENT':
      state = merge(state, {
        inputPage: Page.ConfirmAmount
      })
      break
    case 'navigation/OVERLAY_VISIBLE':
    // if the user dismissed the overlay/keyboard in enter amount
    //  the input component should reset to Page Ready
      if (action.value === false && state.inputPage===Page.EnterAmount) {
        state = merge(state, {
          inputPage: Page.Ready,
          resetClipboard: true
        })
      }
      break
    case 'navigation/SHOW_MODAL':
      if (action.modalState === 'traderScreen') {
        state = merge(state, {
          inputPage: Page.Ready
        })
      }
      break
    case 'sendMoney/ASK_CONTINUE_PAYMENT':
      state = merge(state, {
        alertShouldPopUp: action.value
      })
  }
  return state
}

export default reducer
<commit_msg>update payee to match confirmation response<commit_after>import moment from 'moment'

import { makePayment } from '../../api/payments'
import merge from '../../util/merge'
import { loadMoreTransactions } from './transaction'
import { UNEXPECTED_ERROR, UNAUTHORIZED_ACCESS } from '../../api/apiError'
import { logout } from './login'

const initialState = {
  payeeId: '',
  amount: '',
  amountPaid: '',
  description: '',
  loading: false,
  success: undefined,
  message: '',
  timestamp: undefined,
  inputPage: 0,
  transactionNumber: -1,
  resetClipboard: false,
  alertShouldPopUp: false
}

const Page = {
  Ready: 0,
  EnterAmount: 1,
  ConfirmAmount: 2,
  MakingPayment: 3,
  PaymentComplete: 4
}

export const resetForm = () => ({
  type: 'sendMoney/RESET_FORM'
})

export const updatePayee = (payeeId) => ({
  type: 'sendMoney/UPDATE_PAYEE',
  payeeId
})

export const updatePage = (page) => ({
  type: 'sendMoney/UPDATE_PAGE',
  page
})

export const updateAmount = (amount) => ({
  type: 'sendMoney/UPDATE_AMOUNT',
  amount
})

export const updateDescription = (description) => ({
  type: 'sendMoney/UPDATE_DESCRIPTION',
  description
})

export const returnToPayment = () => ({
  type: 'sendMoney/RETURN_TO_PAYMENT'
})

export const resetPayment = () => ({
  type: 'sendMoney/RESET_PAYMENT'
})

const setLoading = () => ({
  type: 'sendMoney/SET_LOADING'
})

const transactionComplete = (success, message, amountPaid, timestamp, transactionNumber) => ({
  type: 'sendMoney/TRANSACTION_COMPLETE',
  success,
  message,
  amountPaid,
  timestamp,
  transactionNumber
})

export const askToContinuePayment = (value) => ({
  type: 'sendMoney/ASK_CONTINUE_PAYMENT',
  value
})

export const sendTransaction = () =>
  (dispatch, getState) => {
    if (getState().sendMoney.loading) {
      return
    }
    dispatch(setLoading())
    const { payeeId, amount, description } = getState().sendMoney
    makePayment({
        subject: payeeId,
        description: description,
        amount: amount
      }, dispatch)
      .then((result) => {
        dispatch(loadMoreTransactions())
        dispatch(updatePayee(result.toUser.id))
        dispatch(transactionComplete(true, 'Transaction complete', amount, moment(result.date).format('MMMM Do YYYY, h:mm:ss a'), result.transactionNumber))
      })
      .catch(err => {
        if (err.type === UNAUTHORIZED_ACCESS) {
            dispatch(transactionComplete(false, 'Session expired', 0, null, null))
            dispatch(logout())
            dispatch(askToContinuePayment(true))
        } else if (err.type === UNEXPECTED_ERROR) {
          err.response.json()
            .then(json => {
              if (json && json.code === 'dailyAmountExceeded') {
                dispatch(transactionComplete(false, 'Daily amount has been exceeded.', 0, null, null))
              } else if (json && json.code === 'insufficientBalance') {
                dispatch(transactionComplete(false, 'Payment failed.', 0, null, null))
              } else {
                dispatch(transactionComplete(false, 'Error on sending transaction.', 0, null, null))
              }
            })
            .catch(() => dispatch(transactionComplete(false, 'Error on sending transaction.', 0, null, null)))
        } else {
          dispatch(transactionComplete(false, 'Error on sending transaction.', 0, null, null))
        }
      })
  }

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'sendMoney/RESET_FORM':
      state = initialState
      break
    case 'sendMoney/RESET_PAYMENT':
      state = merge(state, {
        amount: '',
        amountPaid: '',
        loading: false,
        success: undefined,
        message: '',
        timestamp: undefined,
        inputPage: 0,
        transactionNumber: -1,
        resetClipboard: false,
        alertShouldPopUp: false
      })
      break
    case 'sendMoney/UPDATE_PAYEE':
      state = merge(state, {
        payeeId: action.payeeId
      })
      break
    case 'sendMoney/UPDATE_AMOUNT':
      state = merge(state, {
        amount: action.amount
      })
      break
    case 'sendMoney/UPDATE_DESCRIPTION':
      state = merge(state, {
        description: action.description
      })
      break
    case 'sendMoney/SET_LOADING':
      state = merge(state, {
        loading: true
      })
      break
    case 'sendMoney/UPDATE_PAGE':
      state = merge(state, {
        inputPage: action.page,
        resetClipboard: false
      })
      break
    case 'sendMoney/TRANSACTION_COMPLETE':
      var stateToUpdate = {
        success: action.success,
        message: action.message,
        amountPaid: action.amountPaid,
        timestamp: action.timestamp,
        loading: false,
        transactionNumber: action.transactionNumber
      }
      if (action.message !== 'Session expired') {
        stateToUpdate.amount = ''
        stateToUpdate.inputPage = Page.PaymentComplete
      }
      state = merge(state, stateToUpdate)
      break
    case 'sendMoney/RETURN_TO_PAYMENT':
      state = merge(state, {
        inputPage: Page.ConfirmAmount
      })
      break
    case 'navigation/OVERLAY_VISIBLE':
    // if the user dismissed the overlay/keyboard in enter amount
    //  the input component should reset to Page Ready
      if (action.value === false && state.inputPage===Page.EnterAmount) {
        state = merge(state, {
          inputPage: Page.Ready,
          resetClipboard: true
        })
      }
      break
    case 'navigation/SHOW_MODAL':
      if (action.modalState === 'traderScreen') {
        state = merge(state, {
          inputPage: Page.Ready
        })
      }
      break
    case 'sendMoney/ASK_CONTINUE_PAYMENT':
      state = merge(state, {
        alertShouldPopUp: action.value
      })
  }
  return state
}

export default reducer
<|endoftext|>


<commit_before>'use strict';

const { RangeError } = require('../errors');
const Util = require('../util/Util');

/**
 * Represents an embed in a message (image/video preview, rich embed, etc.)
 */
class MessageEmbed {
  /**
   * @name MessageEmbed
   * @kind constructor
   * @memberof MessageEmbed
   * @param {MessageEmbed|Object} [data={}] MessageEmbed to clone or raw embed data
   */

  constructor(data = {}, skipValidation = false) {
    this.setup(data, skipValidation);
  }

  setup(data, skipValidation) {
    /**
     * The type of this embed, either:
     * * `rich` - a rich embed
     * * `image` - an image embed
     * * `video` - a video embed
     * * `gifv` - a gifv embed
     * * `article` - an article embed
     * * `link` - a link embed
     * @type {string}
     */
    this.type = data.type;

    /**
     * The title of this embed
     * @type {?string}
     */
    this.title = data.title;

    /**
     * The description of this embed
     * @type {?string}
     */
    this.description = data.description;

    /**
     * The URL of this embed
     * @type {?string}
     */
    this.url = data.url;

    /**
     * The color of this embed
     * @type {?number}
     */
    this.color = Util.resolveColor(data.color);

    /**
     * The timestamp of this embed
     * @type {?number}
     */
    this.timestamp = data.timestamp ? new Date(data.timestamp).getTime() : null;

    /**
     * @typedef {Object} EmbedField
     * @property {string} name The name of this field
     * @property {string} value The value of this field
     * @property {boolean} inline If this field will be displayed inline
     */

    /**
     * The fields of this embed
     * @type {EmbedField[]}
     */
    this.fields = [];
    if (data.fields) {
      this.fields = skipValidation ? data.fields.map(Util.cloneObject) : this.constructor.normalizeFields(data.fields);
    }

    /**
     * @typedef {Object} MessageEmbedThumbnail
     * @property {string} url URL for this thumbnail
     * @property {string} proxyURL ProxyURL for this thumbnail
     * @property {number} height Height of this thumbnail
     * @property {number} width Width of this thumbnail
     */

    /**
     * The thumbnail of this embed (if there is one)
     * @type {?MessageEmbedThumbnail}
     */
    this.thumbnail = data.thumbnail
      ? {
          url: data.thumbnail.url,
          proxyURL: data.thumbnail.proxyURL || data.thumbnail.proxy_url,
          height: data.thumbnail.height,
          width: data.thumbnail.width,
        }
      : null;

    /**
     * @typedef {Object} MessageEmbedImage
     * @property {string} url URL for this image
     * @property {string} proxyURL ProxyURL for this image
     * @property {number} height Height of this image
     * @property {number} width Width of this image
     */

    /**
     * The image of this embed, if there is one
     * @type {?MessageEmbedImage}
     */
    this.image = data.image
      ? {
          url: data.image.url,
          proxyURL: data.image.proxyURL || data.image.proxy_url,
          height: data.image.height,
          width: data.image.width,
        }
      : null;

    /**
     * @typedef {Object} MessageEmbedVideo
     * @property {string} url URL of this video
     * @property {string} proxyURL ProxyURL for this video
     * @property {number} height Height of this video
     * @property {number} width Width of this video
     */

    /**
     * The video of this embed (if there is one)
     * @type {?MessageEmbedVideo}
     * @readonly
     */
    this.video = data.video
      ? {
          url: data.video.url,
          proxyURL: data.video.proxyURL || data.video.proxy_url,
          height: data.video.height,
          width: data.video.width,
        }
      : null;

    /**
     * @typedef {Object} MessageEmbedAuthor
     * @property {string} name The name of this author
     * @property {string} url URL of this author
     * @property {string} iconURL URL of the icon for this author
     * @property {string} proxyIconURL Proxied URL of the icon for this author
     */

    /**
     * The author of this embed (if there is one)
     * @type {?MessageEmbedAuthor}
     */
    this.author = data.author
      ? {
          name: data.author.name,
          url: data.author.url,
          iconURL: data.author.iconURL || data.author.icon_url,
          proxyIconURL: data.author.proxyIconURL || data.author.proxy_icon_url,
        }
      : null;

    /**
     * @typedef {Object} MessageEmbedProvider
     * @property {string} name The name of this provider
     * @property {string} url URL of this provider
     */

    /**
     * The provider of this embed (if there is one)
     * @type {?MessageEmbedProvider}
     */
    this.provider = data.provider
      ? {
          name: data.provider.name,
          url: data.provider.name,
        }
      : null;

    /**
     * @typedef {Object} MessageEmbedFooter
     * @property {string} text The text of this footer
     * @property {string} iconURL URL of the icon for this footer
     * @property {string} proxyIconURL Proxied URL of the icon for this footer
     */

    /**
     * The footer of this embed
     * @type {?MessageEmbedFooter}
     */
    this.footer = data.footer
      ? {
          text: data.footer.text,
          iconURL: data.footer.iconURL || data.footer.icon_url,
          proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url,
        }
      : null;

    /**
     * The files of this embed
     * @type {Array<FileOptions|string|MessageAttachment>}
     */
    this.files = data.files || [];
  }

  /**
   * The date displayed on this embed
   * @type {?Date}
   * @readonly
   */
  get createdAt() {
    return this.timestamp ? new Date(this.timestamp) : null;
  }

  /**
   * The hexadecimal version of the embed color, with a leading hash
   * @type {?string}
   * @readonly
   */
  get hexColor() {
    return this.color ? `#${this.color.toString(16).padStart(6, '0')}` : null;
  }

  /**
   * The accumulated length for the embed title, description, fields and footer text
   * @type {number}
   * @readonly
   */
  get length() {
    return (
      (this.title ? this.title.length : 0) +
      (this.description ? this.description.length : 0) +
      (this.fields.length >= 1
        ? this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0)
        : 0) +
      (this.footer ? this.footer.text.length : 0)
    );
  }

  /**
   * Adds a field to the embed (max 25).
   * @param {StringResolvable} name The name of this field
   * @param {StringResolvable} value The value of this field
   * @param {boolean} [inline=false] If this field will be displayed inline
   * @returns {MessageEmbed}
   */
  addField(name, value, inline) {
    return this.addFields({ name, value, inline });
  }

  /**
   * Adds fields to the embed (max 25).
   * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to add
   * @returns {MessageEmbed}
   */
  addFields(...fields) {
    this.fields.push(...this.constructor.normalizeFields(fields));
    return this;
  }

  /**
   * Removes, replaces, and inserts fields in the embed (max 25).
   * @param {number} index The index to start at
   * @param {number} deleteCount The number of fields to remove
   * @param {...EmbedFieldData|EmbedFieldData[]} [fields] The replacing field objects
   * @returns {MessageEmbed}
   */
  spliceFields(index, deleteCount, ...fields) {
    this.fields.splice(index, deleteCount, ...this.constructor.normalizeFields(...fields));
    return this;
  }

  /**
   * Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when
   * setting an embed image or author/footer icons. Multiple files can be attached.
   * @param {Array<FileOptions|string|MessageAttachment>} files Files to attach
   * @returns {MessageEmbed}
   */
  attachFiles(files) {
    this.files = this.files.concat(files);
    return this;
  }

  /**
   * Sets the author of this embed.
   * @param {StringResolvable} name The name of the author
   * @param {string} [iconURL] The icon URL of the author
   * @param {string} [url] The URL of the author
   * @returns {MessageEmbed}
   */
  setAuthor(name, iconURL, url) {
    this.author = { name: Util.resolveString(name), iconURL, url };
    return this;
  }

  /**
   * Sets the color of this embed.
   * @param {ColorResolvable} color The color of the embed
   * @returns {MessageEmbed}
   */
  setColor(color) {
    this.color = Util.resolveColor(color);
    return this;
  }

  /**
   * Sets the description of this embed.
   * @param {StringResolvable} description The description
   * @returns {MessageEmbed}
   */
  setDescription(description) {
    description = Util.resolveString(description);
    this.description = description;
    return this;
  }

  /**
   * Sets the footer of this embed.
   * @param {StringResolvable} text The text of the footer
   * @param {string} [iconURL] The icon URL of the footer
   * @returns {MessageEmbed}
   */
  setFooter(text, iconURL) {
    text = Util.resolveString(text);
    this.footer = { text, iconURL, proxyIconURL: undefined };
    return this;
  }

  /**
   * Sets the image of this embed.
   * @param {string} url The URL of the image
   * @returns {MessageEmbed}
   */
  setImage(url) {
    this.image = { url };
    return this;
  }

  /**
   * Sets the thumbnail of this embed.
   * @param {string} url The URL of the thumbnail
   * @returns {MessageEmbed}
   */
  setThumbnail(url) {
    this.thumbnail = { url };
    return this;
  }

  /**
   * Sets the timestamp of this embed.
   * @param {Date|number} [timestamp=Date.now()] The timestamp or date
   * @returns {MessageEmbed}
   */
  setTimestamp(timestamp = Date.now()) {
    if (timestamp instanceof Date) timestamp = timestamp.getTime();
    this.timestamp = timestamp;
    return this;
  }

  /**
   * Sets the title of this embed.
   * @param {StringResolvable} title The title
   * @returns {MessageEmbed}
   */
  setTitle(title) {
    title = Util.resolveString(title);
    this.title = title;
    return this;
  }

  /**
   * Sets the URL of this embed.
   * @param {string} url The URL
   * @returns {MessageEmbed}
   */
  setURL(url) {
    this.url = url;
    return this;
  }

  /**
   * Transforms the embed to a plain object.
   * @returns {Object} The raw data of this embed
   */
  toJSON() {
    return {
      title: this.title,
      type: 'rich',
      description: this.description,
      url: this.url,
      timestamp: this.timestamp ? new Date(this.timestamp) : null,
      color: this.color,
      fields: this.fields,
      thumbnail: this.thumbnail,
      image: this.image,
      author: this.author
        ? {
            name: this.author.name,
            url: this.author.url,
            icon_url: this.author.iconURL,
          }
        : null,
      footer: this.footer
        ? {
            text: this.footer.text,
            icon_url: this.footer.iconURL,
          }
        : null,
    };
  }

  /**
   * Normalizes field input and resolves strings.
   * @param {StringResolvable} name The name of the field
   * @param {StringResolvable} value The value of the field
   * @param {boolean} [inline=false] Set the field to display inline
   * @returns {EmbedField}
   */
  static normalizeField(name, value, inline = false) {
    name = Util.resolveString(name);
    if (!name) throw new RangeError('EMBED_FIELD_NAME');
    value = Util.resolveString(value);
    if (!value) throw new RangeError('EMBED_FIELD_VALUE');
    return { name, value, inline };
  }

  /**
   * @typedef {Object} EmbedFieldData
   * @property {StringResolvable} name The name of this field
   * @property {StringResolvable} value The value of this field
   * @property {boolean} [inline] If this field will be displayed inline
   */

  /**
   * Normalizes field input and resolves strings.
   * @param  {...EmbedFieldData|EmbedFieldData[]} fields Fields to normalize
   * @returns {EmbedField[]}
   */
  static normalizeFields(...fields) {
    return fields
      .flat(2)
      .map(field =>
        this.normalizeField(
          field && field.name,
          field && field.value,
          field && typeof field.inline === 'boolean' ? field.inline : false,
        ),
      );
  }
}

module.exports = MessageEmbed;
<commit_msg>docs(Embeds): Added descriptions to the typedefs (#4303)<commit_after>'use strict';

const { RangeError } = require('../errors');
const Util = require('../util/Util');

/**
 * Represents an embed in a message (image/video preview, rich embed, etc.)
 */
class MessageEmbed {
  /**
   * @name MessageEmbed
   * @kind constructor
   * @memberof MessageEmbed
   * @param {MessageEmbed|Object} [data={}] MessageEmbed to clone or raw embed data
   */

  constructor(data = {}, skipValidation = false) {
    this.setup(data, skipValidation);
  }

  setup(data, skipValidation) {
    /**
     * The type of this embed, either:
     * * `rich` - a rich embed
     * * `image` - an image embed
     * * `video` - a video embed
     * * `gifv` - a gifv embed
     * * `article` - an article embed
     * * `link` - a link embed
     * @type {string}
     */
    this.type = data.type;

    /**
     * The title of this embed
     * @type {?string}
     */
    this.title = data.title;

    /**
     * The description of this embed
     * @type {?string}
     */
    this.description = data.description;

    /**
     * The URL of this embed
     * @type {?string}
     */
    this.url = data.url;

    /**
     * The color of this embed
     * @type {?number}
     */
    this.color = Util.resolveColor(data.color);

    /**
     * The timestamp of this embed
     * @type {?number}
     */
    this.timestamp = data.timestamp ? new Date(data.timestamp).getTime() : null;

    /**
     * Represents a field of a MessageEmbed
     * @typedef {Object} EmbedField
     * @property {string} name The name of this field
     * @property {string} value The value of this field
     * @property {boolean} inline If this field will be displayed inline
     */

    /**
     * The fields of this embed
     * @type {EmbedField[]}
     */
    this.fields = [];
    if (data.fields) {
      this.fields = skipValidation ? data.fields.map(Util.cloneObject) : this.constructor.normalizeFields(data.fields);
    }

    /**
     * Represents the thumbnail of a MessageEmbed
     * @typedef {Object} MessageEmbedThumbnail
     * @property {string} url URL for this thumbnail
     * @property {string} proxyURL ProxyURL for this thumbnail
     * @property {number} height Height of this thumbnail
     * @property {number} width Width of this thumbnail
     */

    /**
     * The thumbnail of this embed (if there is one)
     * @type {?MessageEmbedThumbnail}
     */
    this.thumbnail = data.thumbnail
      ? {
          url: data.thumbnail.url,
          proxyURL: data.thumbnail.proxyURL || data.thumbnail.proxy_url,
          height: data.thumbnail.height,
          width: data.thumbnail.width,
        }
      : null;

    /**
     * Represents the image of a MessageEmbed
     * @typedef {Object} MessageEmbedImage
     * @property {string} url URL for this image
     * @property {string} proxyURL ProxyURL for this image
     * @property {number} height Height of this image
     * @property {number} width Width of this image
     */

    /**
     * The image of this embed, if there is one
     * @type {?MessageEmbedImage}
     */
    this.image = data.image
      ? {
          url: data.image.url,
          proxyURL: data.image.proxyURL || data.image.proxy_url,
          height: data.image.height,
          width: data.image.width,
        }
      : null;

    /**
     * Represents the video of a MessageEmbed
     * @typedef {Object} MessageEmbedVideo
     * @property {string} url URL of this video
     * @property {string} proxyURL ProxyURL for this video
     * @property {number} height Height of this video
     * @property {number} width Width of this video
     */

    /**
     * The video of this embed (if there is one)
     * @type {?MessageEmbedVideo}
     * @readonly
     */
    this.video = data.video
      ? {
          url: data.video.url,
          proxyURL: data.video.proxyURL || data.video.proxy_url,
          height: data.video.height,
          width: data.video.width,
        }
      : null;

    /**
     * Represents the author field of a MessageEmbed
     * @typedef {Object} MessageEmbedAuthor
     * @property {string} name The name of this author
     * @property {string} url URL of this author
     * @property {string} iconURL URL of the icon for this author
     * @property {string} proxyIconURL Proxied URL of the icon for this author
     */

    /**
     * The author of this embed (if there is one)
     * @type {?MessageEmbedAuthor}
     */
    this.author = data.author
      ? {
          name: data.author.name,
          url: data.author.url,
          iconURL: data.author.iconURL || data.author.icon_url,
          proxyIconURL: data.author.proxyIconURL || data.author.proxy_icon_url,
        }
      : null;

    /**
     * Represents the provider of a MessageEmbed
     * @typedef {Object} MessageEmbedProvider
     * @property {string} name The name of this provider
     * @property {string} url URL of this provider
     */

    /**
     * The provider of this embed (if there is one)
     * @type {?MessageEmbedProvider}
     */
    this.provider = data.provider
      ? {
          name: data.provider.name,
          url: data.provider.name,
        }
      : null;

    /**
     * Represents the footer field of a MessageEmbed
     * @typedef {Object} MessageEmbedFooter
     * @property {string} text The text of this footer
     * @property {string} iconURL URL of the icon for this footer
     * @property {string} proxyIconURL Proxied URL of the icon for this footer
     */

    /**
     * The footer of this embed
     * @type {?MessageEmbedFooter}
     */
    this.footer = data.footer
      ? {
          text: data.footer.text,
          iconURL: data.footer.iconURL || data.footer.icon_url,
          proxyIconURL: data.footer.proxyIconURL || data.footer.proxy_icon_url,
        }
      : null;

    /**
     * The files of this embed
     * @type {Array<FileOptions|string|MessageAttachment>}
     */
    this.files = data.files || [];
  }

  /**
   * The date displayed on this embed
   * @type {?Date}
   * @readonly
   */
  get createdAt() {
    return this.timestamp ? new Date(this.timestamp) : null;
  }

  /**
   * The hexadecimal version of the embed color, with a leading hash
   * @type {?string}
   * @readonly
   */
  get hexColor() {
    return this.color ? `#${this.color.toString(16).padStart(6, '0')}` : null;
  }

  /**
   * The accumulated length for the embed title, description, fields and footer text
   * @type {number}
   * @readonly
   */
  get length() {
    return (
      (this.title ? this.title.length : 0) +
      (this.description ? this.description.length : 0) +
      (this.fields.length >= 1
        ? this.fields.reduce((prev, curr) => prev + curr.name.length + curr.value.length, 0)
        : 0) +
      (this.footer ? this.footer.text.length : 0)
    );
  }

  /**
   * Adds a field to the embed (max 25).
   * @param {StringResolvable} name The name of this field
   * @param {StringResolvable} value The value of this field
   * @param {boolean} [inline=false] If this field will be displayed inline
   * @returns {MessageEmbed}
   */
  addField(name, value, inline) {
    return this.addFields({ name, value, inline });
  }

  /**
   * Adds fields to the embed (max 25).
   * @param {...EmbedFieldData|EmbedFieldData[]} fields The fields to add
   * @returns {MessageEmbed}
   */
  addFields(...fields) {
    this.fields.push(...this.constructor.normalizeFields(fields));
    return this;
  }

  /**
   * Removes, replaces, and inserts fields in the embed (max 25).
   * @param {number} index The index to start at
   * @param {number} deleteCount The number of fields to remove
   * @param {...EmbedFieldData|EmbedFieldData[]} [fields] The replacing field objects
   * @returns {MessageEmbed}
   */
  spliceFields(index, deleteCount, ...fields) {
    this.fields.splice(index, deleteCount, ...this.constructor.normalizeFields(...fields));
    return this;
  }

  /**
   * Sets the file to upload alongside the embed. This file can be accessed via `attachment://fileName.extension` when
   * setting an embed image or author/footer icons. Multiple files can be attached.
   * @param {Array<FileOptions|string|MessageAttachment>} files Files to attach
   * @returns {MessageEmbed}
   */
  attachFiles(files) {
    this.files = this.files.concat(files);
    return this;
  }

  /**
   * Sets the author of this embed.
   * @param {StringResolvable} name The name of the author
   * @param {string} [iconURL] The icon URL of the author
   * @param {string} [url] The URL of the author
   * @returns {MessageEmbed}
   */
  setAuthor(name, iconURL, url) {
    this.author = { name: Util.resolveString(name), iconURL, url };
    return this;
  }

  /**
   * Sets the color of this embed.
   * @param {ColorResolvable} color The color of the embed
   * @returns {MessageEmbed}
   */
  setColor(color) {
    this.color = Util.resolveColor(color);
    return this;
  }

  /**
   * Sets the description of this embed.
   * @param {StringResolvable} description The description
   * @returns {MessageEmbed}
   */
  setDescription(description) {
    description = Util.resolveString(description);
    this.description = description;
    return this;
  }

  /**
   * Sets the footer of this embed.
   * @param {StringResolvable} text The text of the footer
   * @param {string} [iconURL] The icon URL of the footer
   * @returns {MessageEmbed}
   */
  setFooter(text, iconURL) {
    text = Util.resolveString(text);
    this.footer = { text, iconURL, proxyIconURL: undefined };
    return this;
  }

  /**
   * Sets the image of this embed.
   * @param {string} url The URL of the image
   * @returns {MessageEmbed}
   */
  setImage(url) {
    this.image = { url };
    return this;
  }

  /**
   * Sets the thumbnail of this embed.
   * @param {string} url The URL of the thumbnail
   * @returns {MessageEmbed}
   */
  setThumbnail(url) {
    this.thumbnail = { url };
    return this;
  }

  /**
   * Sets the timestamp of this embed.
   * @param {Date|number} [timestamp=Date.now()] The timestamp or date
   * @returns {MessageEmbed}
   */
  setTimestamp(timestamp = Date.now()) {
    if (timestamp instanceof Date) timestamp = timestamp.getTime();
    this.timestamp = timestamp;
    return this;
  }

  /**
   * Sets the title of this embed.
   * @param {StringResolvable} title The title
   * @returns {MessageEmbed}
   */
  setTitle(title) {
    title = Util.resolveString(title);
    this.title = title;
    return this;
  }

  /**
   * Sets the URL of this embed.
   * @param {string} url The URL
   * @returns {MessageEmbed}
   */
  setURL(url) {
    this.url = url;
    return this;
  }

  /**
   * Transforms the embed to a plain object.
   * @returns {Object} The raw data of this embed
   */
  toJSON() {
    return {
      title: this.title,
      type: 'rich',
      description: this.description,
      url: this.url,
      timestamp: this.timestamp ? new Date(this.timestamp) : null,
      color: this.color,
      fields: this.fields,
      thumbnail: this.thumbnail,
      image: this.image,
      author: this.author
        ? {
            name: this.author.name,
            url: this.author.url,
            icon_url: this.author.iconURL,
          }
        : null,
      footer: this.footer
        ? {
            text: this.footer.text,
            icon_url: this.footer.iconURL,
          }
        : null,
    };
  }

  /**
   * Normalizes field input and resolves strings.
   * @param {StringResolvable} name The name of the field
   * @param {StringResolvable} value The value of the field
   * @param {boolean} [inline=false] Set the field to display inline
   * @returns {EmbedField}
   */
  static normalizeField(name, value, inline = false) {
    name = Util.resolveString(name);
    if (!name) throw new RangeError('EMBED_FIELD_NAME');
    value = Util.resolveString(value);
    if (!value) throw new RangeError('EMBED_FIELD_VALUE');
    return { name, value, inline };
  }

  /**
   * @typedef {Object} EmbedFieldData
   * @property {StringResolvable} name The name of this field
   * @property {StringResolvable} value The value of this field
   * @property {boolean} [inline] If this field will be displayed inline
   */

  /**
   * Normalizes field input and resolves strings.
   * @param  {...EmbedFieldData|EmbedFieldData[]} fields Fields to normalize
   * @returns {EmbedField[]}
   */
  static normalizeFields(...fields) {
    return fields
      .flat(2)
      .map(field =>
        this.normalizeField(
          field && field.name,
          field && field.value,
          field && typeof field.inline === 'boolean' ? field.inline : false,
        ),
      );
  }
}

module.exports = MessageEmbed;
<|endoftext|>


<commit_before>module.exports = function( http, userHandle ){
  var qs = require('querystring'),
      basicAuth = require( 'express').basicAuth,
      env = require('../../config/environment'),
      routes = {
        site: require('./controllers/site'),
        user: require('./controllers/user')(userHandle)
      },
      userList = env.get( "ALLOWED_USERS" );

  userList = qs.parse( userList, ",", ":" );

  // Shared middleware
  var authenticate = basicAuth( function( user, pass ) {
      for ( var username in userList ) {
        if ( userList.hasOwnProperty( username ) ) {
          return ( user === username && pass === userList[ username ] );
        }
      }
      return false;
    });

  // Static pages
  http.get('/', authenticate, routes.site.index);
  http.get('/console', authenticate, routes.site.console);

  // Resources
  http.get('/js/sso-ux.js', routes.site.sso);
  http.get('/js/console.js', routes.site.consolejs);
  http.get('/ajax/forms/new_user.html', routes.user.userForm);

  // LoginAPI
  http.get('/user/:id', routes.user.get);
  http.get('/users', routes.user.all);
  http.put('/user/:id', routes.user.update);
  http.del('/user/:id', authenticate, routes.user.del);
  http.post('/user', authenticate, routes.user.create);
  http.get('/user/subdomain/:name', authenticate, routes.user.checkSubdomain);
  http.get( '/isAdmin', authenticate, routes.user.isAdmin );

  // Devops
  http.get('/healthcheck', routes.site.healthcheck);

  // Dev Tools
  //
  // FIXME: delete this as soon as we hit prod, PLEASE!!
  // SEE: https://bugzilla.mozilla.org/show_bug.cgi?id=872204
  http.get('/dev/delete', routes.user.devDelete);
};

<commit_msg>l37 clearance<commit_after>module.exports = function( http, userHandle ){
  var qs = require('querystring'),
      basicAuth = require( 'express').basicAuth,
      env = require('../../config/environment'),
      routes = {
        site: require('./controllers/site'),
        user: require('./controllers/user')(userHandle)
      },
      userList = env.get( "ALLOWED_USERS" );

  userList = qs.parse( userList, ",", ":" );

  // Shared middleware
  var authenticate = basicAuth( function( user, pass ) {
      for ( var username in userList ) {
        if ( userList.hasOwnProperty( username ) ) {
          return ( user === username && pass === userList[ username ] );
        }
      }
      return false;
    });

  // Static pages
  http.get('/', authenticate, routes.site.index);
  http.get('/console', authenticate, routes.site.console);

  // Resources
  http.get('/js/sso-ux.js', routes.site.sso);
  http.get('/js/console.js', routes.site.consolejs);
  http.get('/ajax/forms/new_user.html', routes.user.userForm);

  // LoginAPI
  http.get('/user/:id', routes.user.get);
  http.get('/users', routes.user.all);
  http.put('/user/:id', routes.user.update);
  http.del('/user/:id', authenticate, routes.user.del);
  http.post('/user', routes.user.create);
  http.get('/user/subdomain/:name', authenticate, routes.user.checkSubdomain);
  http.get( '/isAdmin', authenticate, routes.user.isAdmin );

  // Devops
  http.get('/healthcheck', routes.site.healthcheck);

  // Dev Tools
  //
  // FIXME: delete this as soon as we hit prod, PLEASE!!
  // SEE: https://bugzilla.mozilla.org/show_bug.cgi?id=872204
  http.get('/dev/delete', routes.user.devDelete);
};

<|endoftext|>