<commit_before>#!/usr/bin/env node

var argv = require('yargs').argv;
var color = require('cli-color');
var restler;
var moment;
var cheerio;
var html2text;
var wrap;

var i18n = {
  'источник': {
    en: 'source'
  }
};

var gold = function(s) {
  return Meduza.settings.color ? color.xterm(215)(s) : s;
};
var gray = function(s) {
  return Meduza.settings.color ? color.xterm(245)(s) : s;
};
var dark = function(s) {
  return Meduza.settings.color ? color.xterm(237)(s) : s;
};
var underline = function(s) {
  return Meduza.settings.color ? color.underline(s) : s;
};

var Meduza = {
  settings: {},
  chronos: {
    news: { ru: 'Новости' },
    cards: { ru: 'Картотека' },
    articles: { ru: 'Истории' },
    shapito: { ru: 'Шапито' },
    polygon: { ru: 'Полигон' }
  },
  spinner: undefined,
  i18n: {
    'источник': 'source'
  },

  translate: function(s) {
    return this.settings.locale === 'en' ? this.i18n[s] || '[' + s + ']' : s;
  },
  parseArgv: function() {
    var settings = {
      help: argv.help || argv.h,
      version: argv.version || argv.v,
      locale: argv.english ? 'en' : 'ru',
      chrono: argv.type || argv.t ? argv.type || argv.t : 'news',
      show: argv.show || argv.s ? argv.show || argv.s : undefined,
      color: !(argv.color === false),
      wrap: 80,
      number: parseInt(argv.number || argv.n)
    };

    if (isNaN(settings.number)) {
      settings.number = 10;
    } else if (settings.number < 1) {
      settings.number = 10;
    } else if (settings.number > 30) {
      settings.number = 30;
    }

    var chronos_arr = Object.keys(this.chronos);

    for (var i = 0, l = argv._.length; i < l; i++) {
      if (argv._[i] === 'en') {
        settings.locale = argv._[i];
      } else if (argv._[i].search(':') > -1) {
        settings.show = argv._[i];
      } else if (chronos_arr.indexOf(argv._[i]) > -1) {
        settings.chrono = argv._[i];
      }
    }

    this.settings = settings;
  },
  showHelp: function() {
    var help =
      '\n' +
      'Usage: ' + underline(gold('meduza')) + ' [commands/options]\n' +
      '\n' +
      'Commands: ' + gray('(you can combine every command with each other)\n') +
      '\n' +
      '  meduza           \tOutput latest news.\n' +
      '  meduza en        \tOutput latest news from english version.\n' +
      '  meduza <time>    \tOutput article by time (i.e. 15:42).\n' +
      '  meduza <type>    \tChoose articles type (default: news). Only one and only in russian.\n' +
      '\n' +
      'Options:\n' +
      '\n' +
      '  -t, --type <type>\tChoose articles type (default: news). Only one and only in russian.\n' +
      '      --english    \tOutput latest news from english version.\n' +
      '  -s, --show <time>\tOutput article by time (i.e. 15:42).\n' +
      '  -n, --number <num>\tNumber of news in output (from 1 to 30).\n' +
      '      --no-color   \tOutput without colors.\n' +
      '  -v, --version    \tDisplay version.\n' +
      '  -h, --help       \tDisplay help information.\n' +
      '\n' +
      'Categories:        \tnews, cards, articles, shapito, polygon.\n';

    console.log(help);
    return true;
  },
  showLogo: function() {
    var logo =
      "\n" +
      "                         @@#`                                \n" +
      "                         @@@@@@;                             \n" +
      " '@@# @@@# @@@:    ;@@.     .#@@+ ;@@  @@@   :@@+    `#@@@'  \n" +
      ":;@@@';@@@;;@@+  @@@'@@; @@@  @@+ ;@@` @@@  `+@@@@     :@@@  \n" +
      "  @@@  @@@  @@+  @@@'@;  @@@  @@+ ;@@` @@@     ,#    ,@#@@@  \n" +
      "  @@@  @@@  @@+  @@@     @@@  @@+ ;@@` @@@    #,   :@@,`@@@  \n" +
      "  @@@  @@@  @@+, @@@     @@@@+@@+ ;@@,,@@@.: @@@@@::@@;,@@@.:\n" +
      "  @@@  @@@ `@@@. #@@@@+   `@@@@   ;@@@ @@@@   #@@@ ,@@@`@@@@ " + gray('v' + require('./package.json').version);

    console.log(gold(logo));
  },
  showVersion: function() {
    console.log(require('./package.json').version);
    return true;
  },
  showDateLine: function(timestamp) {
    var string = '══ ' + moment(timestamp, 'X').format('LL') + ' ';
    var len = this.settings.wrap - string.length;
    string += (new Array(len)).join('═');
    console.log('\n' + string + '\n');
  },
  showArticleShort: function(doc) {
    var time = moment(doc.published_at, 'X').format('H:mm');
    var title = wrap(doc.title);
    var secondTitle = doc.second_title ? wrap(doc.second_title) : null;
    var type = doc.tag ? doc.tag.name : doc.document_type;
    var url = 'meduza.io/' + doc.url;

    console.log(gray(time) + ' ' + gold(type.toUpperCase()));
    console.log(title);
    if (secondTitle) console.log(secondTitle);
    console.log(dark(url));
  },
  showArticleFull: function(doc) {
    var type = doc.tag.name;
    var title = doc.title;
    var time = moment(doc.published_at, 'X').format('LLL');
    var source = doc.source ? doc.source.name : '';

    var $ = cheerio.load(doc.content.body);

//    console.log(doc);

    var text_lead = wrap(html2text.fromString($('.Lead').html(), { wordwrap: null }));
    var text_body = wrap(html2text.fromString($('.Body').html(), { wordwrap: null }));
    var text_card = wrap(html2text.fromString($('.Card').html(), { wordwrap: null }));
    var text_auth = wrap(html2text.fromString($('.Authors').html(), { wordwrap: null }));
    var text_quot = wrap(html2text.fromString($('.SourceQuote').html(), { wordwrap: null }));

    var text =
      (text_lead !== 'null' ? text_lead + '\n\n' : '') +
      (text_body !== 'null' ? text_body + '\n\n' : '') +
      (text_card !== 'null' ? text_card + '\n\n' : '') +
      (text_auth !== 'null' ? text_auth + '\n\n' : '') +
      (text_quot !== 'null' ? gray((new Array(this.settings.wrap)).join('·')) + '\n' + gray(text_quot) + '\n' + gray((new Array(this.settings.wrap)).join('·')) + '\n' : '');

    var contextEls = $('.Context-item');
    var context = '
';
    contextEls.each(function(idx, el) {
      context += '
' + $(el).text() + '
';
    });

    context += '
';
    context = html2text.fromString(context);

    console.log(gold(type.toUpperCase()));
    console.log(title);
    console.log(gray(time + (!source || source !== 'Meduza' ? ', ' + this.translate('источник') + ': ' + source : '')));
    console.log('');
    console.log(text.trim());

    if(contextEls.length) {
      console.log('');
      console.log(' ' + context);
    }

    console.log('');
  },
  spinnerShow: function() {
    if (!this.spinner) {
      var Spinner = require('cli-spinner').Spinner;
      this.spinner = new Spinner('%s');
      this.spinner.setSpinnerString('⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈');
      this.spinner.setSpinnerDelay(50);
    }
    this.spinner.start();
  },
  spinnerHide: function() {
    this.spinner.stop(true);
  },
  getArticles: function() {
    var _this = this;

    restler
      .get('https://meduza.io/api/v3/search?chrono=' + this.settings.chrono + '&page=0&per_page=' + this.settings.number + '&locale=' + this.settings.locale)
      .on('complete', function(data) {
        _this.spinnerHide();

        var collection = data.collection;
        var documents = data.documents;
        var momentBefore;
        var momentNow;

        if (!_this.settings.show) _this.showLogo();

        for (var i = 0; i < _this.settings.number; i++) {
          var doc = documents[collection[i]];

          if (!_this.settings.show) {
            momentNow = moment(doc.published_at, 'X').format('DDD');
            if (typeof momentBefore === 'undefined' || momentBefore !== momentNow) {
              _this.showDateLine(doc.published_at);
            }

            momentBefore = momentNow;
            _this.showArticleShort(doc);
          } else {
            var time = moment(doc.published_at, 'X').format('H:mm');
            if (time === _this.settings.show) {
              _this.spinnerShow();
              restler.get('https://meduza.io/api/v3/' + doc.url)
                .on('complete', function(data) {
                  _this.spinnerHide();
                  _this.showArticleFull(data.root);
                });
            }
          }
        }
      })
  },

  init: function() {
    this.parseArgv();

    if (this.settings.version) return this.showVersion();
    if (this.settings.help) return this.showHelp();

    wrap = require('wordwrap')(this.settings.wrap);
    html2text = require('html-to-text');
    cheerio = require('cheerio');
    moment = require('moment');
    moment.locale(this.settings.locale);
    restler = require('restler');

    this.spinnerShow();
    this.getArticles();
  }
};

Meduza.init();

module.exports = Meduza;<commit_msg>Правка вывода промо-статей<commit_after>#!/usr/bin/env node

var argv = require('yargs').argv;
var color = require('cli-color');
var restler;
var moment;
var cheerio;
var html2text;
var wrap;

var i18n = {
  'источник': {
    en: 'source'
  }
};

var gold = function(s) {
  return Meduza.settings.color ? color.xterm(215)(s) : s;
};
var gray = function(s) {
  return Meduza.settings.color ? color.xterm(245)(s) : s;
};
var dark = function(s) {
  return Meduza.settings.color ? color.xterm(237)(s) : s;
};
var underline = function(s) {
  return Meduza.settings.color ? color.underline(s) : s;
};

var Meduza = {
  settings: {},
  chronos: {
    news: { ru: 'Новости' },
    cards: { ru: 'Картотека' },
    articles: { ru: 'Истории' },
    shapito: { ru: 'Шапито' },
    polygon: { ru: 'Полигон' }
  },
  spinner: undefined,
  i18n: {
    'источник': 'source'
  },

  translate: function(s) {
    return this.settings.locale === 'en' ? this.i18n[s] || '[' + s + ']' : s;
  },
  parseArgv: function() {
    var settings = {
      help: argv.help || argv.h,
      version: argv.version || argv.v,
      locale: argv.english ? 'en' : 'ru',
      chrono: argv.type || argv.t ? argv.type || argv.t : 'news',
      show: argv.show || argv.s ? argv.show || argv.s : undefined,
      color: !(argv.color === false),
      wrap: 80,
      number: parseInt(argv.number || argv.n)
    };

    if (isNaN(settings.number)) {
      settings.number = 10;
    } else if (settings.number < 1) {
      settings.number = 10;
    } else if (settings.number > 30) {
      settings.number = 30;
    }

    var chronos_arr = Object.keys(this.chronos);

    for (var i = 0, l = argv._.length; i < l; i++) {
      if (argv._[i] === 'en') {
        settings.locale = argv._[i];
      } else if (argv._[i].search(':') > -1) {
        settings.show = argv._[i];
      } else if (chronos_arr.indexOf(argv._[i]) > -1) {
        settings.chrono = argv._[i];
      }
    }

    this.settings = settings;
  },
  showHelp: function() {
    var help =
      '\n' +
      'Usage: ' + underline(gold('meduza')) + ' [commands/options]\n' +
      '\n' +
      'Commands: ' + gray('(you can combine every command with each other)\n') +
      '\n' +
      '  meduza           \tOutput latest news.\n' +
      '  meduza en        \tOutput latest news from english version.\n' +
      '  meduza <time>    \tOutput article by time (i.e. 15:42).\n' +
      '  meduza <type>    \tChoose articles type (default: news). Only one and only in russian.\n' +
      '\n' +
      'Options:\n' +
      '\n' +
      '  -t, --type <type>\tChoose articles type (default: news). Only one and only in russian.\n' +
      '      --english    \tOutput latest news from english version.\n' +
      '  -s, --show <time>\tOutput article by time (i.e. 15:42).\n' +
      '  -n, --number <num>\tNumber of news in output (from 1 to 30).\n' +
      '      --no-color   \tOutput without colors.\n' +
      '  -v, --version    \tDisplay version.\n' +
      '  -h, --help       \tDisplay help information.\n' +
      '\n' +
      'Categories:        \tnews, cards, articles, shapito, polygon.\n';

    console.log(help);
    return true;
  },
  showLogo: function() {
    var logo =
      "\n" +
      "                         @@#`                                \n" +
      "                         @@@@@@;                             \n" +
      " '@@# @@@# @@@:    ;@@.     .#@@+ ;@@  @@@   :@@+    `#@@@'  \n" +
      ":;@@@';@@@;;@@+  @@@'@@; @@@  @@+ ;@@` @@@  `+@@@@     :@@@  \n" +
      "  @@@  @@@  @@+  @@@'@;  @@@  @@+ ;@@` @@@     ,#    ,@#@@@  \n" +
      "  @@@  @@@  @@+  @@@     @@@  @@+ ;@@` @@@    #,   :@@,`@@@  \n" +
      "  @@@  @@@  @@+, @@@     @@@@+@@+ ;@@,,@@@.: @@@@@::@@;,@@@.:\n" +
      "  @@@  @@@ `@@@. #@@@@+   `@@@@   ;@@@ @@@@   #@@@ ,@@@`@@@@ " + gray('v' + require('./package.json').version);

    console.log(gold(logo));
  },
  showVersion: function() {
    console.log(require('./package.json').version);
    return true;
  },
  showDateLine: function(timestamp) {
    var string = '══ ' + moment(timestamp, 'X').format('LL') + ' ';
    var len = this.settings.wrap - string.length;
    string += (new Array(len)).join('═');
    console.log('\n' + string + '\n');
  },
  showArticleShort: function(doc) {
    var time = moment(doc.published_at, 'X').format('H:mm');
    var title = wrap(doc.title);
    var secondTitle = doc.second_title ? wrap(doc.second_title) : null;
    var type = doc.tag ? doc.tag.name : doc.document_type;
    var url = doc.document_type === 'promo' ? doc.promo_url.replace('http://', '') : 'meduza.io/' + doc.url;

    console.log(gray(time) + ' ' + gold(type.toUpperCase()));
    console.log(title);
    if (secondTitle) console.log(secondTitle);
    console.log(dark(url));
  },
  showArticleFull: function(doc) {
    var type = doc.tag.name;
    var title = doc.title;
    var time = moment(doc.published_at, 'X').format('LLL');
    var source = doc.source ? doc.source.name : '';

    var text;

    if(!doc.content.body) {
      text = gray('→ ') + gold(doc.promo_url);
    } else {
      var $ = cheerio.load(doc.content.body);
      var text_lead = wrap(html2text.fromString($('.Lead').html(), { wordwrap: null }));
      var text_body = wrap(html2text.fromString($('.Body').html(), { wordwrap: null }));
      var text_card = wrap(html2text.fromString($('.Card').html(), { wordwrap: null }));
      var text_auth = wrap(html2text.fromString($('.Authors').html(), { wordwrap: null }));
      var text_quot = wrap(html2text.fromString($('.SourceQuote').html(), { wordwrap: null }));

      text =
        (text_lead !== 'null' ? text_lead + '\n\n' : '') +
        (text_body !== 'null' ? text_body + '\n\n' : '') +
        (text_card !== 'null' ? text_card + '\n\n' : '') +
        (text_auth !== 'null' ? text_auth + '\n\n' : '') +
        (text_quot !== 'null' ? gray((new Array(this.settings.wrap)).join('·')) + '\n' + gray(text_quot) + '\n' + gray((new Array(this.settings.wrap)).join('·')) + '\n' : '');

      var contextEls = $('.Context-item');
      var context = '
';
      contextEls.each(function(idx, el) {
        context += '
' + $(el).text() + '
';
      });

      context += '
';
      context = html2text.fromString(context);
    }

    console.log(gold(type.toUpperCase()));
    console.log(title);
    console.log(gray(time + (!source || source !== 'Meduza' ? ', ' + this.translate('источник') + ': ' + source : '')));
    console.log('');
    console.log(text.trim());

    if(contextEls && contextEls.length) {
      console.log('');
      console.log(' ' + context);
    }

    console.log('');
  },
  spinnerShow: function() {
    if (!this.spinner) {
      var Spinner = require('cli-spinner').Spinner;
      this.spinner = new Spinner('%s');
      this.spinner.setSpinnerString('⠁⠁⠉⠙⠚⠒⠂⠂⠒⠲⠴⠤⠄⠄⠤⠠⠠⠤⠦⠖⠒⠐⠐⠒⠓⠋⠉⠈⠈');
      this.spinner.setSpinnerDelay(50);
    }
    this.spinner.start();
  },
  spinnerHide: function() {
    this.spinner.stop(true);
  },
  getArticles: function() {
    this.spinnerShow();
    var _this = this;

    restler
      .get('https://meduza.io/api/v3/search?chrono=' + this.settings.chrono + '&page=0&per_page=' + this.settings.number + '&locale=' + this.settings.locale)
      .on('complete', function(data) {
        _this.spinnerHide();

        var collection = data.collection;
        var documents = data.documents;
        var momentBefore;
        var momentNow;

        if (!_this.settings.show) _this.showLogo();

        for (var i = 0; i < _this.settings.number; i++) {
          var doc = documents[collection[i]];

          if (!_this.settings.show) {
            momentNow = moment(doc.published_at, 'X').format('DDD');
            if (typeof momentBefore === 'undefined' || momentBefore !== momentNow) {
              _this.showDateLine(doc.published_at);
            }

            momentBefore = momentNow;
            _this.showArticleShort(doc);
          } else {
            var time = moment(doc.published_at, 'X').format('H:mm');
            if (time === _this.settings.show) {
              _this.spinnerShow();
              restler.get('https://meduza.io/api/v3/' + doc.url)
                .on('complete', function(data) {
                  _this.spinnerHide();
                  _this.showArticleFull(data.root);
                });
            }
          }
        }
      })
  },

  init: function() {
    this.parseArgv();

    if (this.settings.version) return this.showVersion();
    if (this.settings.help) return this.showHelp();

    wrap = require('wordwrap')(this.settings.wrap);
    html2text = require('html-to-text');
    cheerio = require('cheerio');
    moment = require('moment');
    moment.locale(this.settings.locale);
    restler = require('restler');

    this.getArticles();
  }
};

Meduza.init();

module.exports = Meduza;<|endoftext|>


<commit_before>pc.extend(pc, function () {
    var ImageElement = function ImageElement (element) {
        this._element = element;
        this._entity = element.entity;
        this._system = element.system;

        // public
        this._textureAsset = null;
        this._texture = null;
        this._materialAsset = null;
        this._material = null;

        this._rect = new pc.Vec4(0,0,1,1); // x, y, w, h

        this._color = new pc.Color(1,1,1,1);

        // private
        this._positions = [];
        this._normals = [];
        this._uvs = [];
        this._indices = [];

        this._mesh = this._createMesh();
        this._node = new pc.GraphNode();
        this._model = new pc.Model();
        this._model.graph = this._node;
        this._meshInstance = new pc.MeshInstance(this._node, this._mesh, this._material);
        this._meshInstance.castShadow = false;
        this._meshInstance.receiveShadow = false;
        this._model.meshInstances.push(this._meshInstance);
        this._drawOrder = 0;

        this._entity.addChild(this._model.graph);
        this._model._entity = this._entity;

        // initialize based on screen
        this._onScreenChange(this._element.screen);

        // listen for events
        this._element.on('resize', this._onParentResize, this);
        this._element.on('screen:set:screenspace', this._onScreenSpaceChange, this);
        this._element.on('set:screen', this._onScreenChange, this);
        this._element.on('set:draworder', this._onDrawOrderChange, this);
        this._element.on('screen:set:resolution', this._onResolutionChange, this);
    };

    pc.extend(ImageElement.prototype, {
        destroy: function () {
            if (this._model) {
                this._system.app.scene.removeModel(this._model);
                this._model.destroy();
                this._model = null;
            }

            this._element.off('resize', this._onParentResize, this);
            this._element.off('screen:set:screenspace', this._onScreenSpaceChange, this);
            this._element.off('set:screen', this._onScreenChange, this);
            this._element.off('set:draworder', this._onDrawOrderChange, this);
            this._element.off('screen:set:resolution', this._onResolutionChange, this);
        },

        _onResolutionChange: function (res) {
        },

        _onParentResize: function () {
            if (this._mesh) this._updateMesh(this._mesh);
        },

        _onScreenSpaceChange: function (value) {
            this._updateMaterial(value);
        },

        _onScreenChange: function (screen) {
            if (screen) {
                this._updateMaterial(screen.screen.screenSpace);
            } else {
                this._updateMaterial(false);
            }
        },

        _onDrawOrderChange: function (order) {
            this._drawOrder = order;
            if (this._meshInstance) {
                this._meshInstance.drawOrder = order;
            }
        },

        // Returns true if we are using a material
        // other than the default materials
        _hasUserMaterial: function () {
            return !!this._materialAsset ||
                   (!!this._material &&
                   this._material !== this._system.defaultScreenSpaceImageMaterial &&
                   this._material !== this._system.defaultImageMaterial);
        },

        _updateMaterial: function (screenSpace) {
            if (screenSpace) {
                if (!this._hasUserMaterial()) {
                    this._material = this._system.defaultScreenSpaceImageMaterial;
                }
                if (this._meshInstance) this._meshInstance.layer = pc.scene.LAYER_HUD;
            } else {
                if (!this._hasUserMaterial()) {
                    this._material = this._system.defaultImageMaterial;
                }
                if (this._meshInstance) this._meshInstance.layer = pc.scene.LAYER_WORLD;
            }
            if (this._meshInstance) {
                this._meshInstance.material = this._material;
                this._meshInstance.screenSpace = screenSpace;
            }
        },

        // build a quad for the image
        _createMesh: function () {
            var w = this._element.width;
            var h = this._element.height;

            this._positions[0] = 0;
            this._positions[1] = 0;
            this._positions[2] = 0;
            this._positions[3] = w;
            this._positions[4] = 0;
            this._positions[5] = 0;
            this._positions[6] = w;
            this._positions[7] = h;
            this._positions[8] = 0;
            this._positions[9] = 0;
            this._positions[10] = h;
            this._positions[11] = 0;

            for (var i = 0; i < 12; i+=3) {
                this._normals[i] = 0;
                this._normals[i+1] = 0;
                this._normals[i+2] = 1;
            }

            this._uvs[0] = this._rect.data[0];
            this._uvs[1] = this._rect.data[1];
            this._uvs[2] = this._rect.data[0] + this._rect.data[2];
            this._uvs[3] = this._rect.data[1];
            this._uvs[4] = this._rect.data[0] + this._rect.data[2];
            this._uvs[5] = this._rect.data[1] + this._rect.data[3];
            this._uvs[6] = this._rect.data[0];
            this._uvs[7] = this._rect.data[1] + this._rect.data[3];

            this._indices[0] = 0;
            this._indices[1] = 1;
            this._indices[2] = 3;
            this._indices[3] = 2;
            this._indices[4] = 3;
            this._indices[5] = 1;

            var mesh = pc.createMesh(this._system.app.graphicsDevice, this._positions, {uvs: this._uvs, normals: this._normals, indices: this._indices});
            this._updateMesh(mesh);

            return mesh;
        },

        _updateMesh: function (mesh) {
            var w = this._element.width;
            var h = this._element.height;

            // update material
            if (this._element.screen) {
                this._updateMaterial(this._element.screen.screen.screenSpace);
            } else {
                this._updateMaterial();
            }

            this._positions[0] = 0;
            this._positions[1] = 0;
            this._positions[2] = 0;
            this._positions[3] = w;
            this._positions[4] = 0;
            this._positions[5] = 0;
            this._positions[6] = w;
            this._positions[7] = h;
            this._positions[8] = 0;
            this._positions[9] = 0;
            this._positions[10] = h;
            this._positions[11] = 0;

            // offset for pivot
            var hp = this._element.pivot.data[0];
            var vp = this._element.pivot.data[1];

            for (var i = 0; i < this._positions.length; i += 3) {
                this._positions[i] -= hp*w;
                this._positions[i+1] -= vp*h;
            }

            this._uvs[0] = this._rect.data[0];
            this._uvs[1] = this._rect.data[1];
            this._uvs[2] = this._rect.data[0] + this._rect.data[2];
            this._uvs[3] = this._rect.data[1];
            this._uvs[4] = this._rect.data[0] + this._rect.data[2];
            this._uvs[5] = this._rect.data[1] + this._rect.data[3];
            this._uvs[6] = this._rect.data[0];
            this._uvs[7] = this._rect.data[1] + this._rect.data[3];

            var vb = mesh.vertexBuffer;
            var it = new pc.VertexIterator(vb);
            var numVertices = 4;
            for (var i = 0; i < numVertices; i++) {
                it.element[pc.SEMANTIC_POSITION].set(this._positions[i*3+0], this._positions[i*3+1], this._positions[i*3+2]);
                it.element[pc.SEMANTIC_NORMAL].set(this._normals[i*3+0], this._normals[i*3+1], this._normals[i*3+2]);
                it.element[pc.SEMANTIC_TEXCOORD0].set(this._uvs[i*2+0], this._uvs[i*2+1]);

                it.next();
            }
            it.end();

            mesh.aabb.compute(this._positions);

            // force update meshInstance aabb
            if (this._meshInstance)
                this._meshInstance._aabbVer = -1;
        },

        _onMaterialLoad: function (asset) {
            this.material = asset.resource;
        },

        _onMaterialChange: function () {

        },

        _onMaterialRemove: function () {

        },

        _onTextureAdded: function (asset) {
            this._system.app.assets.off('add:' + asset.id, this._onTextureAdded, this);
            if (this._textureAsset === asset.id) {
                this._bindTextureAsset(asset);
            }
        },

        _bindTextureAsset: function (asset) {
            asset.on("load", this._onTextureLoad, this);
            asset.on("change", this._onTextureChange, this);
            asset.on("remove", this._onTextureRemove, this);

            if (asset.resource) {
                this._onTextureLoad(asset);
            } else {
                this._system.app.assets.load(asset);
            }
        },

        _onTextureLoad: function (asset) {
            this.texture = asset.resource;
        },

        _onTextureChange: function (asset) {

        },

        _onTextureRemove: function (asset) {

        },

        onEnable: function () {
            if (this._model && !this._system.app.scene.containsModel(this._model)) {
                this._system.app.scene.addModel(this._model);
            }
        },

        onDisable: function () {
            if (this._model && this._system.app.scene.containsModel(this._model)) {
                this._system.app.scene.removeModel(this._model);
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "color", {
        get: function () {
            return this._color;
        },

        set: function (value) {
            this._color.data[0] = value.data[0];
            this._color.data[1] = value.data[1];
            this._color.data[2] = value.data[2];

            if (this._meshInstance) {
                this._meshInstance.setParameter('material_emissive', this._color.data3);
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "opacity", {
        get: function () {
            return this._color.data[3];
        },

        set: function (value) {
            this._color.data[3] = value;
            this._meshInstance.setParameter("material_opacity", value);
        }
    });

    Object.defineProperty(ImageElement.prototype, "rect", {
        get: function () {
            return this._rect;
        },

        set: function (value) {
            if (value instanceof pc.Vec4) {
                this._rect.set(value.x, value.y, value.z, value.w);
            } else {
                this._rect.set(value[0], value[1], value[2], value[3]);
            }
            if (this._mesh) this._updateMesh(this._mesh);
        }
    });

    Object.defineProperty(ImageElement.prototype, "material", {
        get: function () {
            return this._material;
        },
        set: function (value) {
            if (! value) {
                var screenSpace = this._element.screen ? this._element.screen.screen.screenSpace : false;
                value = screenSpace ? this._system.defaultScreenSpaceImageMaterial : this._system.defaultImageMaterial;
            }

            this._material = value;
            if (value) {
                this._meshInstance.material = value;

                // if this is not the default material then clear color and opacity overrides
                if (value !== this._system.defaultScreenSpaceImageMaterial && value !== this._system.defaultImageMaterial) {
                    this._meshInstance.deleteParameter('material_opacity');
                    this._meshInstance.deleteParameter('material_emissive');
                }
                // otherwise if we are back to the defaults reset the color and opacity
                else {
                    this._meshInstance.setParameter('material_emissive', this._color.data3);
                    this._meshInstance.setParameter('material_opacity', this._color.data[3]);
                }
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "materialAsset", {
        get: function () {
            return this._materialAsset;
        },

        set: function (value) {
            var assets = this._system.app.assets;
            var _id = value;

            if (value instanceof pc.Asset) {
                _id = value.id;
            }

            if (this._materialAsset !== _id) {
                if (this._materialAsset) {
                    var _prev = assets.get(this._materialAsset);

                    _prev.off("load", this._onMaterialLoad, this);
                    _prev.off("change", this._onMaterialChange, this);
                    _prev.off("remove", this._onMaterialRemove, this);
                }

                this._materialAsset = _id;
                if (this._materialAsset) {
                    var asset = assets.get(this._materialAsset);

                    asset.on("load", this._onMaterialLoad, this);
                    asset.on("change", this._onMaterialChange, this);
                    asset.on("remove", this._onMaterialRemove, this);

                    if (asset.resource) {
                        this._onMaterialLoad(asset);
                    } else {
                        assets.load(asset);
                    }
                } else {
                    this.material = null;
                }
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "texture", {
        get: function () {
            return this._texture;
        },
        set: function (value) {
            this._texture = value;

            if (value) {
                // default texture just uses emissive and opacity maps
                this._meshInstance.setParameter("texture_emissiveMap", this._texture);
                this._meshInstance.setParameter("texture_opacityMap", this._texture);
                this._meshInstance.setParameter("material_emissive", this._color.data3);
                this._meshInstance.setParameter("material_opacity", this._color.data[3]);
            } else {
                // clear texture params
                this._meshInstance.deleteParameter("texture_emissiveMap");
                this._meshInstance.deleteParameter("texture_opacityMap");
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "textureAsset", {
        get: function () {
            return this._textureAsset;
        },

        set: function (value) {
            var assets = this._system.app.assets;
            var _id = value;

            if (value instanceof pc.Asset) {
                _id = value.id;
            }

            if (this._textureAsset !== _id) {
                if (this._textureAsset) {
                    var _prev = assets.get(this._textureAsset);

                    _prev.off("load", this._onTextureLoad, this);
                    _prev.off("change", this._onTextureChange, this);
                    _prev.off("remove", this._onTextureRemove, this);
                }

                this._textureAsset = _id;
                if (this._textureAsset) {
                    var asset = assets.get(this._textureAsset);
                    if (! asset) {
                        assets.on('add:' + this._textureAsset, this._onTextureAdded, this);
                    } else {
                        this._bindTextureAsset(asset);
                    }
                } else {
                    this.texture = null;
                }
            }
        }
    });

    return {
        ImageElement: ImageElement
    };
}());

<commit_msg>[FIX] Handle missing material / texture assets for image elements<commit_after>pc.extend(pc, function () {
    var ImageElement = function ImageElement (element) {
        this._element = element;
        this._entity = element.entity;
        this._system = element.system;

        // public
        this._textureAsset = null;
        this._texture = null;
        this._materialAsset = null;
        this._material = null;

        this._rect = new pc.Vec4(0,0,1,1); // x, y, w, h

        this._color = new pc.Color(1,1,1,1);

        // private
        this._positions = [];
        this._normals = [];
        this._uvs = [];
        this._indices = [];

        this._mesh = this._createMesh();
        this._node = new pc.GraphNode();
        this._model = new pc.Model();
        this._model.graph = this._node;
        this._meshInstance = new pc.MeshInstance(this._node, this._mesh, this._material);
        this._meshInstance.castShadow = false;
        this._meshInstance.receiveShadow = false;
        this._model.meshInstances.push(this._meshInstance);
        this._drawOrder = 0;

        this._entity.addChild(this._model.graph);
        this._model._entity = this._entity;

        // initialize based on screen
        this._onScreenChange(this._element.screen);

        // listen for events
        this._element.on('resize', this._onParentResize, this);
        this._element.on('screen:set:screenspace', this._onScreenSpaceChange, this);
        this._element.on('set:screen', this._onScreenChange, this);
        this._element.on('set:draworder', this._onDrawOrderChange, this);
        this._element.on('screen:set:resolution', this._onResolutionChange, this);
    };

    pc.extend(ImageElement.prototype, {
        destroy: function () {
            if (this._model) {
                this._system.app.scene.removeModel(this._model);
                this._model.destroy();
                this._model = null;
            }

            this._element.off('resize', this._onParentResize, this);
            this._element.off('screen:set:screenspace', this._onScreenSpaceChange, this);
            this._element.off('set:screen', this._onScreenChange, this);
            this._element.off('set:draworder', this._onDrawOrderChange, this);
            this._element.off('screen:set:resolution', this._onResolutionChange, this);
        },

        _onResolutionChange: function (res) {
        },

        _onParentResize: function () {
            if (this._mesh) this._updateMesh(this._mesh);
        },

        _onScreenSpaceChange: function (value) {
            this._updateMaterial(value);
        },

        _onScreenChange: function (screen) {
            if (screen) {
                this._updateMaterial(screen.screen.screenSpace);
            } else {
                this._updateMaterial(false);
            }
        },

        _onDrawOrderChange: function (order) {
            this._drawOrder = order;
            if (this._meshInstance) {
                this._meshInstance.drawOrder = order;
            }
        },

        // Returns true if we are using a material
        // other than the default materials
        _hasUserMaterial: function () {
            return !!this._materialAsset ||
                   (!!this._material &&
                   this._material !== this._system.defaultScreenSpaceImageMaterial &&
                   this._material !== this._system.defaultImageMaterial);
        },

        _updateMaterial: function (screenSpace) {
            if (screenSpace) {
                if (!this._hasUserMaterial()) {
                    this._material = this._system.defaultScreenSpaceImageMaterial;
                }
                if (this._meshInstance) this._meshInstance.layer = pc.scene.LAYER_HUD;
            } else {
                if (!this._hasUserMaterial()) {
                    this._material = this._system.defaultImageMaterial;
                }
                if (this._meshInstance) this._meshInstance.layer = pc.scene.LAYER_WORLD;
            }
            if (this._meshInstance) {
                this._meshInstance.material = this._material;
                this._meshInstance.screenSpace = screenSpace;
            }
        },

        // build a quad for the image
        _createMesh: function () {
            var w = this._element.width;
            var h = this._element.height;

            this._positions[0] = 0;
            this._positions[1] = 0;
            this._positions[2] = 0;
            this._positions[3] = w;
            this._positions[4] = 0;
            this._positions[5] = 0;
            this._positions[6] = w;
            this._positions[7] = h;
            this._positions[8] = 0;
            this._positions[9] = 0;
            this._positions[10] = h;
            this._positions[11] = 0;

            for (var i = 0; i < 12; i+=3) {
                this._normals[i] = 0;
                this._normals[i+1] = 0;
                this._normals[i+2] = 1;
            }

            this._uvs[0] = this._rect.data[0];
            this._uvs[1] = this._rect.data[1];
            this._uvs[2] = this._rect.data[0] + this._rect.data[2];
            this._uvs[3] = this._rect.data[1];
            this._uvs[4] = this._rect.data[0] + this._rect.data[2];
            this._uvs[5] = this._rect.data[1] + this._rect.data[3];
            this._uvs[6] = this._rect.data[0];
            this._uvs[7] = this._rect.data[1] + this._rect.data[3];

            this._indices[0] = 0;
            this._indices[1] = 1;
            this._indices[2] = 3;
            this._indices[3] = 2;
            this._indices[4] = 3;
            this._indices[5] = 1;

            var mesh = pc.createMesh(this._system.app.graphicsDevice, this._positions, {uvs: this._uvs, normals: this._normals, indices: this._indices});
            this._updateMesh(mesh);

            return mesh;
        },

        _updateMesh: function (mesh) {
            var w = this._element.width;
            var h = this._element.height;

            // update material
            if (this._element.screen) {
                this._updateMaterial(this._element.screen.screen.screenSpace);
            } else {
                this._updateMaterial();
            }

            this._positions[0] = 0;
            this._positions[1] = 0;
            this._positions[2] = 0;
            this._positions[3] = w;
            this._positions[4] = 0;
            this._positions[5] = 0;
            this._positions[6] = w;
            this._positions[7] = h;
            this._positions[8] = 0;
            this._positions[9] = 0;
            this._positions[10] = h;
            this._positions[11] = 0;

            // offset for pivot
            var hp = this._element.pivot.data[0];
            var vp = this._element.pivot.data[1];

            for (var i = 0; i < this._positions.length; i += 3) {
                this._positions[i] -= hp*w;
                this._positions[i+1] -= vp*h;
            }

            this._uvs[0] = this._rect.data[0];
            this._uvs[1] = this._rect.data[1];
            this._uvs[2] = this._rect.data[0] + this._rect.data[2];
            this._uvs[3] = this._rect.data[1];
            this._uvs[4] = this._rect.data[0] + this._rect.data[2];
            this._uvs[5] = this._rect.data[1] + this._rect.data[3];
            this._uvs[6] = this._rect.data[0];
            this._uvs[7] = this._rect.data[1] + this._rect.data[3];

            var vb = mesh.vertexBuffer;
            var it = new pc.VertexIterator(vb);
            var numVertices = 4;
            for (var i = 0; i < numVertices; i++) {
                it.element[pc.SEMANTIC_POSITION].set(this._positions[i*3+0], this._positions[i*3+1], this._positions[i*3+2]);
                it.element[pc.SEMANTIC_NORMAL].set(this._normals[i*3+0], this._normals[i*3+1], this._normals[i*3+2]);
                it.element[pc.SEMANTIC_TEXCOORD0].set(this._uvs[i*2+0], this._uvs[i*2+1]);

                it.next();
            }
            it.end();

            mesh.aabb.compute(this._positions);

            // force update meshInstance aabb
            if (this._meshInstance)
                this._meshInstance._aabbVer = -1;
        },

        _onMaterialLoad: function (asset) {
            this.material = asset.resource;
        },

        _onMaterialAdded: function (asset) {
            this._system.app.assets.off('add:' + asset.id, this._onMaterialAdded, this);
            if (this._materialAsset === asset.id) {
                this._bindMaterialAsset(asset);
            }
        },

        _bindMaterialAsset: function (asset) {
            asset.on("load", this._onMaterialLoad, this);
            asset.on("change", this._onMaterialChange, this);
            asset.on("remove", this._onMaterialRemove, this);

            if (asset.resource) {
                this._onMaterialLoad(asset);
             } else {
                this._system.app.assets.load(asset);
             }
        },

        _onMaterialChange: function () {

        },

        _onMaterialRemove: function () {

        },

        _onTextureAdded: function (asset) {
            this._system.app.assets.off('add:' + asset.id, this._onTextureAdded, this);
            if (this._textureAsset === asset.id) {
                this._bindTextureAsset(asset);
            }
        },

        _bindTextureAsset: function (asset) {
            asset.on("load", this._onTextureLoad, this);
            asset.on("change", this._onTextureChange, this);
            asset.on("remove", this._onTextureRemove, this);

            if (asset.resource) {
                this._onTextureLoad(asset);
            } else {
                this._system.app.assets.load(asset);
            }
        },

        _onTextureLoad: function (asset) {
            this.texture = asset.resource;
        },

        _onTextureChange: function (asset) {

        },

        _onTextureRemove: function (asset) {

        },

        onEnable: function () {
            if (this._model && !this._system.app.scene.containsModel(this._model)) {
                this._system.app.scene.addModel(this._model);
            }
        },

        onDisable: function () {
            if (this._model && this._system.app.scene.containsModel(this._model)) {
                this._system.app.scene.removeModel(this._model);
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "color", {
        get: function () {
            return this._color;
        },

        set: function (value) {
            this._color.data[0] = value.data[0];
            this._color.data[1] = value.data[1];
            this._color.data[2] = value.data[2];

            if (this._meshInstance) {
                this._meshInstance.setParameter('material_emissive', this._color.data3);
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "opacity", {
        get: function () {
            return this._color.data[3];
        },

        set: function (value) {
            this._color.data[3] = value;
            this._meshInstance.setParameter("material_opacity", value);
        }
    });

    Object.defineProperty(ImageElement.prototype, "rect", {
        get: function () {
            return this._rect;
        },

        set: function (value) {
            if (value instanceof pc.Vec4) {
                this._rect.set(value.x, value.y, value.z, value.w);
            } else {
                this._rect.set(value[0], value[1], value[2], value[3]);
            }
            if (this._mesh) this._updateMesh(this._mesh);
        }
    });

    Object.defineProperty(ImageElement.prototype, "material", {
        get: function () {
            return this._material;
        },
        set: function (value) {
            if (! value) {
                var screenSpace = this._element.screen ? this._element.screen.screen.screenSpace : false;
                value = screenSpace ? this._system.defaultScreenSpaceImageMaterial : this._system.defaultImageMaterial;
            }

            this._material = value;
            if (value) {
                this._meshInstance.material = value;

                // if this is not the default material then clear color and opacity overrides
                if (value !== this._system.defaultScreenSpaceImageMaterial && value !== this._system.defaultImageMaterial) {
                    this._meshInstance.deleteParameter('material_opacity');
                    this._meshInstance.deleteParameter('material_emissive');
                }
                // otherwise if we are back to the defaults reset the color and opacity
                else {
                    this._meshInstance.setParameter('material_emissive', this._color.data3);
                    this._meshInstance.setParameter('material_opacity', this._color.data[3]);
                }
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "materialAsset", {
        get: function () {
            return this._materialAsset;
        },

        set: function (value) {
            var assets = this._system.app.assets;
            var _id = value;

            if (value instanceof pc.Asset) {
                _id = value.id;
            }

            if (this._materialAsset !== _id) {
                if (this._materialAsset) {
                    var _prev = assets.get(this._materialAsset);
                    if (_prev) {
                        _prev.off("load", this._onMaterialLoad, this);
                        _prev.off("change", this._onMaterialChange, this);
                        _prev.off("remove", this._onMaterialRemove, this);
                    }
                }

                this._materialAsset = _id;
                if (this._materialAsset) {
                    var asset = assets.get(this._materialAsset);
                    if (! asset) {
                        this.material = null;
                        assets.on('add:' + this._materialAsset, this._onMaterialAdded, this);
                    } else {
                        this._bindMaterialAsset(asset);
                    }
                } else {
                    this.material = null;
                }
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "texture", {
        get: function () {
            return this._texture;
        },
        set: function (value) {
            this._texture = value;

            if (value) {
                // default texture just uses emissive and opacity maps
                this._meshInstance.setParameter("texture_emissiveMap", this._texture);
                this._meshInstance.setParameter("texture_opacityMap", this._texture);
                this._meshInstance.setParameter("material_emissive", this._color.data3);
                this._meshInstance.setParameter("material_opacity", this._color.data[3]);
            } else {
                // clear texture params
                this._meshInstance.deleteParameter("texture_emissiveMap");
                this._meshInstance.deleteParameter("texture_opacityMap");
            }
        }
    });

    Object.defineProperty(ImageElement.prototype, "textureAsset", {
        get: function () {
            return this._textureAsset;
        },

        set: function (value) {
            var assets = this._system.app.assets;
            var _id = value;

            if (value instanceof pc.Asset) {
                _id = value.id;
            }

            if (this._textureAsset !== _id) {
                if (this._textureAsset) {
                    var _prev = assets.get(this._textureAsset);
                    if (_prev) {
                        _prev.off("load", this._onTextureLoad, this);
                        _prev.off("change", this._onTextureChange, this);
                        _prev.off("remove", this._onTextureRemove, this);
                    }
                }

                this._textureAsset = _id;
                if (this._textureAsset) {
                    var asset = assets.get(this._textureAsset);
                    if (! asset) {
                        this.texture = null;
                        assets.on('add:' + this._textureAsset, this._onTextureAdded, this);
                    } else {
                        this._bindTextureAsset(asset);
                    }
                } else {
                    this.texture = null;
                }
            }
        }
    });

    return {
        ImageElement: ImageElement
    };
}());

<|endoftext|>


<commit_before>var SecureStorage, SecureStorageiOS, SecureStorageAndroid, SecureStorageWindows, SecureStorageBrowser;
var sjcl_ss = cordova.require('cordova-plugin-secure-storage.sjcl_ss');
var _AES_PARAM = {
    ks: 256,
    ts: 128,
    mode: 'ccm',
    cipher: 'aes'
};

var _checkCallbacks = function (success, error) {
    if (typeof success != 'function') {
        throw new Error('SecureStorage failure: success callback parameter must be a function');
    }
    if (typeof error != 'function') {
        throw new Error('SecureStorage failure: error callback parameter must be a function');
    }
};

var _merge_options = function (defaults, options){
    var res = {};
    var attrname;

    for (attrname in defaults) {
        res[attrname] = defaults[attrname];
    }
    for (attrname in options) {
        if (res.hasOwnProperty(attrname)) {
            res[attrname] = options[attrname];
        } else {
            throw new Error('SecureStorage failure: invalid option ' + attrname);
        }
    }

    return res;
};

/**
 * Helper method to execute Cordova native method
 *
 * @param   {String}    nativeMethodName Method to execute.
 * @param   {Array}     args             Execution arguments.
 * @param   {Function}  success          Called when returning successful result from an action.
 * @param   {Function}  error            Called when returning error result from an action.
 *
 */
var _executeNativeMethod = function (success, error, nativeMethodName, args) {
    var fail;
    // args checking
    _checkCallbacks(success, error);

    // By convention a failure callback should always receive an instance
    // of a JavaScript Error object.
    fail = function(err) {
        // provide default message if no details passed to callback
        if (typeof err === 'undefined') {
            error(new Error('Error occured while executing native method.'));
        } else {
            // wrap string to Error instance if necessary
            error(typeof err === 'string' ? new Error(err) : err);
        }
    };

    cordova.exec(success, fail, 'SecureStorage', nativeMethodName, args);
};

SecureStorageiOS = function (success, error, service) {
    this.service = service;
    setTimeout(success, 0);
    return this;
};

SecureStorageiOS.prototype = {
    get: function (success, error, key) {
        try {
            _executeNativeMethod(success, error, 'get', [this.service, key]);
        } catch (e) {
            error(e);
        }
    },

    set: function (success, error, key, value) {
        try {
            _executeNativeMethod(success, error, 'set', [this.service, key, value]);
        } catch (e) {
            error(e);
        }
    },

    remove: function (success, error, key) {
        try {
            _executeNativeMethod(success, error, 'remove', [this.service, key]);
        } catch (e) {
            error(e);
        }
    }
};

// SecureStorage for Windows web interface and proxy parameters are the same as on iOS
// so we don't create own definition for Windows and simply re-use iOS
SecureStorageWindows = SecureStorageiOS;

SecureStorageAndroid = function (success, error, service, options) {
    var self = this;
    if (options) {
        this.options = _merge_options(this.options, options);
    }

    this.service = service;
    try {
        _executeNativeMethod(
            function (native_aes_supported) {
                var checkMigrateToNativeAES;

                checkMigrateToNativeAES = function () {
                    self.options.native = native_aes_supported && self.options.native;
                    if (!self.options.native){
                        success();
                    } else {
                        _executeNativeMethod(
                            function () {
                                success();
                            },
                            function() {
                                self._migrate_to_native_aes(success);
                            },
                            'fetch',
                            ['_SS_MIGRATED_TO_NATIVE']
                        );
                    }
                };

                if (self.options.migrateLocalStorage){
                    self._migrate_to_native_storage(checkMigrateToNativeAES);
                } else {
                    _executeNativeMethod(
                        function () {
                            checkMigrateToNativeAES();
                        },
                        function() {
                            self._migrate_to_native_storage(checkMigrateToNativeAES);
                        },
                        'fetch',
                        ['_SS_MIGRATED_TO_NATIVE_STORAGE']
                    );
                }
            },
            error,
            'init',
            [this.service]
        );
    } catch (e) {
        error(e);
    }
    return this;
};

SecureStorageAndroid.prototype = {
    options: {
        native: true,
        migrateLocalStorage: false
    },

    get: function (success, error, key) {
        if (this.options.native) {
            this._native_get(success, error, key);
        } else {
            this._sjcl_get(success, error, key);
        }
    },

    set: function (success, error, key, value) {
        if (this.options.native) {
            this._native_set(success, error, key, value);
        } else {
            this._sjcl_set(success, error, key, value);
        }
    },

    remove: function (success, error, key) {
        _executeNativeMethod(
            function () {
                success(key);
            },
            error,
            'remove',
            ['_SS_' + key]
        );
    },

    secureDevice: function (success, error) {
        _executeNativeMethod(
            success,
            error,
            'secureDevice',
            []
        );
    },

    clear: function (success, error) {
        _executeNativeMethod(
            success,
            error,
            'clear',
            []
        );
    },

    _fetch: function (success, error, key) {
        _executeNativeMethod(
            function (value) {
                success(value);
            },
            error,
            'fetch',
            ['_SS_' + key]
        );
    },

    _sjcl_get: function (success, error, key) {
        var payload;
        try {
            _executeNativeMethod(
                function (value) {
                    payload = JSON.parse(value);
                    _executeNativeMethod(
                        function (AESKey) {
                            var value, AESKeyBits;
                            try {
                                AESKeyBits = sjcl_ss.codec.base64.toBits(AESKey);
                                value = sjcl_ss.decrypt(AESKeyBits, payload.value);
                                success(value);
                            } catch (e) {
                                error(e);
                            }
                        },
                        error,
                        'decrypt_rsa',
                        [payload.key]
                    );
                },
                error,
                'fetch',
                ['_SS_' + key]
            );

        } catch (e) {
            error(e);
        }
    },

    _sjcl_set: function (success, error, key, value) {
        var AESKey, encValue;

        try {
            AESKey = sjcl_ss.random.randomWords(8);
            _AES_PARAM.adata = this.service;
            encValue = sjcl_ss.encrypt(AESKey, value, _AES_PARAM);
            // Encrypt the AES key
            _executeNativeMethod(
                function (encKey) {
                    _executeNativeMethod(
                        function () {
                            success(key);
                        },
                        error,
                        'store',
                        ['_SS_' + key, JSON.stringify({key: encKey, value: encValue})]
                    );
                },
                error,
                'encrypt_rsa',
                [sjcl_ss.codec.base64.fromBits(AESKey)]
            );
        } catch (e) {
            error(e);
        }
    },

    _native_get: function (success, error, key) {
        _executeNativeMethod(
            success,
            error,
            'get',
            ['_SS_' + key]
        );
    },

    _native_set: function (success, error, key, value) {
        _executeNativeMethod(
            function () {
                success(key);
            },
            error,
            'set',
            ['_SS_' + key, value, this.service]
        );
    },

    _migrate_to_native_storage: function (success) {
        var key;
        var secureStorageData = [];
        var entriesCnt;
        var entriesProcessed = 0;
        var entryMigrated;
        var migrated;
        var migrateEntry;

        migrated = function () {
            _executeNativeMethod(
                function () {
                    success();
                },
                function () {
                },
                'store',
                ['_SS_MIGRATED_TO_NATIVE_STORAGE', '1']
            );
        };

        entryMigrated = function () {
            entriesProcessed++;
            if (entriesProcessed === entriesCnt) {
                migrated();
            }
        };

        migrateEntry = function (key, value) {
            _executeNativeMethod(
                function () {
                    localStorage.removeItem(key);
                    entryMigrated();
                },
                function () {
                },
                'store',
                [key, value]
            );
        };

        for (key in localStorage) {
            if (localStorage.hasOwnProperty(key) && key.indexOf('_SS_') === 0) {
                secureStorageData[key] = localStorage.getItem(key);
            }
        }
        entriesCnt = secureStorageData.length;
        if (entriesCnt === 0) {
            migrated();
        }
        for (key in secureStorageData) {
            if (secureStorageData.hasOwnProperty(key)) {
                migrateEntry(key, secureStorageData[key]);
            }
        }
    },

    _migrate_to_native_aes: function (success) {
        var self = this;
        var keysCnt, keysProcessed;
        var keyProcessed;
        var migrated;
        var migrateKey;
        var i;

        migrated = function () {
            _executeNativeMethod(
                function () {
                    success();
                },
                function () {
                },
                'store',
                ['_SS_MIGRATED_TO_NATIVE', '1']
            );
        };

        keyProcessed = function () {
            keysProcessed++;
            if (keysProcessed === keysCnt) {
                migrated();
            }
        };

        migrateKey = function (key) {
            var payload;
            _executeNativeMethod(
                function (value) {
                    payload = JSON.parse(value);
                    if (!payload.native) {
                        self._sjcl_get(
                            function (value) {
                                self._native_set(
                                    function () {
                                        keyProcessed();
                                    },
                                    function () {},
                                    key.replace('_SS_', ''),
                                    value
                                );
                            },
                            function () {},
                            key.replace('_SS_', '')
                        );
                    } else {
                        keyProcessed();
                    }
                },
                function () {
                },
                'fetch',
                [key]
            );
        };

        _executeNativeMethod(
            function (keys) {
                keysProcessed = 0;
                keysCnt = keys.length;
                if (keysCnt === 0) {
                    migrated();
                } else {
                    for (i = 0; i < keysCnt; i++) {
                        migrateKey(keys[i]);
                    }
                }
            },
            function () {
            },
            'keys',
            []
        );
    }
};

SecureStorageBrowser = function (success, error, service) {
    this.service = service;
    setTimeout(success, 0);
    return this;
};

SecureStorageBrowser.prototype = {

    get: function (success, error, key) {
        var value;
        try {
            _checkCallbacks(success, error);
            value = localStorage.getItem('_SS_' + key);
            if (!value) {
                error(new Error('Key "' + key + '" not found.'));
            } else {
                success(value);
            }
        } catch (e) {
            error(e);
        }
    },

    set: function (success, error, key, value) {
        try {
            _checkCallbacks(success, error);
            localStorage.setItem('_SS_' + key, value);
            success(key);
        } catch (e) {
            error(e);
        }
    },
    remove: function (success, error, key) {
        localStorage.removeItem('_SS_' + key);
        success(key);
    }
};

switch (cordova.platformId) {
case 'ios':
    SecureStorage = SecureStorageiOS;
    break;
case 'android':
    SecureStorage = SecureStorageAndroid;
    break;
case 'windows':
    SecureStorage = SecureStorageWindows;
    break;
case 'browser':
    SecureStorage = SecureStorageBrowser;
    break;
default:
    SecureStorage = null;
}

if (!cordova.plugins) {
    cordova.plugins = {};
}

if (!cordova.plugins.SecureStorage) {
    cordova.plugins.SecureStorage = SecureStorage;
}

if (typeof module != 'undefined' && module.exports) {
    module.exports = SecureStorage;
}
<commit_msg>expose keys method from android, and trim key prefix<commit_after>var SecureStorage, SecureStorageiOS, SecureStorageAndroid, SecureStorageWindows, SecureStorageBrowser;
var sjcl_ss = cordova.require('cordova-plugin-secure-storage.sjcl_ss');
var _AES_PARAM = {
    ks: 256,
    ts: 128,
    mode: 'ccm',
    cipher: 'aes'
};

var _checkCallbacks = function (success, error) {
    if (typeof success != 'function') {
        throw new Error('SecureStorage failure: success callback parameter must be a function');
    }
    if (typeof error != 'function') {
        throw new Error('SecureStorage failure: error callback parameter must be a function');
    }
};

var _merge_options = function (defaults, options){
    var res = {};
    var attrname;

    for (attrname in defaults) {
        res[attrname] = defaults[attrname];
    }
    for (attrname in options) {
        if (res.hasOwnProperty(attrname)) {
            res[attrname] = options[attrname];
        } else {
            throw new Error('SecureStorage failure: invalid option ' + attrname);
        }
    }

    return res;
};

/**
 * Helper method to execute Cordova native method
 *
 * @param   {String}    nativeMethodName Method to execute.
 * @param   {Array}     args             Execution arguments.
 * @param   {Function}  success          Called when returning successful result from an action.
 * @param   {Function}  error            Called when returning error result from an action.
 *
 */
var _executeNativeMethod = function (success, error, nativeMethodName, args) {
    var fail;
    // args checking
    _checkCallbacks(success, error);

    // By convention a failure callback should always receive an instance
    // of a JavaScript Error object.
    fail = function(err) {
        // provide default message if no details passed to callback
        if (typeof err === 'undefined') {
            error(new Error('Error occured while executing native method.'));
        } else {
            // wrap string to Error instance if necessary
            error(typeof err === 'string' ? new Error(err) : err);
        }
    };

    cordova.exec(success, fail, 'SecureStorage', nativeMethodName, args);
};

SecureStorageiOS = function (success, error, service) {
    this.service = service;
    setTimeout(success, 0);
    return this;
};

SecureStorageiOS.prototype = {
    get: function (success, error, key) {
        try {
            _executeNativeMethod(success, error, 'get', [this.service, key]);
        } catch (e) {
            error(e);
        }
    },

    set: function (success, error, key, value) {
        try {
            _executeNativeMethod(success, error, 'set', [this.service, key, value]);
        } catch (e) {
            error(e);
        }
    },

    remove: function (success, error, key) {
        try {
            _executeNativeMethod(success, error, 'remove', [this.service, key]);
        } catch (e) {
            error(e);
        }
    }
};

// SecureStorage for Windows web interface and proxy parameters are the same as on iOS
// so we don't create own definition for Windows and simply re-use iOS
SecureStorageWindows = SecureStorageiOS;

SecureStorageAndroid = function (success, error, service, options) {
    var self = this;
    if (options) {
        this.options = _merge_options(this.options, options);
    }

    this.service = service;
    try {
        _executeNativeMethod(
            function (native_aes_supported) {
                var checkMigrateToNativeAES;

                checkMigrateToNativeAES = function () {
                    self.options.native = native_aes_supported && self.options.native;
                    if (!self.options.native){
                        success();
                    } else {
                        _executeNativeMethod(
                            function () {
                                success();
                            },
                            function() {
                                self._migrate_to_native_aes(success);
                            },
                            'fetch',
                            ['_SS_MIGRATED_TO_NATIVE']
                        );
                    }
                };

                if (self.options.migrateLocalStorage){
                    self._migrate_to_native_storage(checkMigrateToNativeAES);
                } else {
                    _executeNativeMethod(
                        function () {
                            checkMigrateToNativeAES();
                        },
                        function() {
                            self._migrate_to_native_storage(checkMigrateToNativeAES);
                        },
                        'fetch',
                        ['_SS_MIGRATED_TO_NATIVE_STORAGE']
                    );
                }
            },
            error,
            'init',
            [this.service]
        );
    } catch (e) {
        error(e);
    }
    return this;
};

SecureStorageAndroid.prototype = {
    options: {
        native: true,
        migrateLocalStorage: false
    },

    get: function (success, error, key) {
        if (this.options.native) {
            this._native_get(success, error, key);
        } else {
            this._sjcl_get(success, error, key);
        }
    },

    set: function (success, error, key, value) {
        if (this.options.native) {
            this._native_set(success, error, key, value);
        } else {
            this._sjcl_set(success, error, key, value);
        }
    },

    keys: function (success, error) {
        _executeNativeMethod(
            function (ret) {
                var i, len = ret.length, keys = [];
                for (i = 0; i < len; ++i) {
                    if (ret[i] && ret[i].slice(0, 4) === '_SS_') {
                        keys.push(ret[i].slice(4));
                    }
                }
                success(keys);
            },
            error,
            'keys',
            []
        );
    },

    remove: function (success, error, key) {
        _executeNativeMethod(
            function () {
                success(key);
            },
            error,
            'remove',
            ['_SS_' + key]
        );
    },

    secureDevice: function (success, error) {
        _executeNativeMethod(
            success,
            error,
            'secureDevice',
            []
        );
    },

    clear: function (success, error) {
        _executeNativeMethod(
            success,
            error,
            'clear',
            []
        );
    },

    _fetch: function (success, error, key) {
        _executeNativeMethod(
            function (value) {
                success(value);
            },
            error,
            'fetch',
            ['_SS_' + key]
        );
    },

    _sjcl_get: function (success, error, key) {
        var payload;
        try {
            _executeNativeMethod(
                function (value) {
                    payload = JSON.parse(value);
                    _executeNativeMethod(
                        function (AESKey) {
                            var value, AESKeyBits;
                            try {
                                AESKeyBits = sjcl_ss.codec.base64.toBits(AESKey);
                                value = sjcl_ss.decrypt(AESKeyBits, payload.value);
                                success(value);
                            } catch (e) {
                                error(e);
                            }
                        },
                        error,
                        'decrypt_rsa',
                        [payload.key]
                    );
                },
                error,
                'fetch',
                ['_SS_' + key]
            );

        } catch (e) {
            error(e);
        }
    },

    _sjcl_set: function (success, error, key, value) {
        var AESKey, encValue;

        try {
            AESKey = sjcl_ss.random.randomWords(8);
            _AES_PARAM.adata = this.service;
            encValue = sjcl_ss.encrypt(AESKey, value, _AES_PARAM);
            // Encrypt the AES key
            _executeNativeMethod(
                function (encKey) {
                    _executeNativeMethod(
                        function () {
                            success(key);
                        },
                        error,
                        'store',
                        ['_SS_' + key, JSON.stringify({key: encKey, value: encValue})]
                    );
                },
                error,
                'encrypt_rsa',
                [sjcl_ss.codec.base64.fromBits(AESKey)]
            );
        } catch (e) {
            error(e);
        }
    },

    _native_get: function (success, error, key) {
        _executeNativeMethod(
            success,
            error,
            'get',
            ['_SS_' + key]
        );
    },

    _native_set: function (success, error, key, value) {
        _executeNativeMethod(
            function () {
                success(key);
            },
            error,
            'set',
            ['_SS_' + key, value, this.service]
        );
    },

    _migrate_to_native_storage: function (success) {
        var key;
        var secureStorageData = [];
        var entriesCnt;
        var entriesProcessed = 0;
        var entryMigrated;
        var migrated;
        var migrateEntry;

        migrated = function () {
            _executeNativeMethod(
                function () {
                    success();
                },
                function () {
                },
                'store',
                ['_SS_MIGRATED_TO_NATIVE_STORAGE', '1']
            );
        };

        entryMigrated = function () {
            entriesProcessed++;
            if (entriesProcessed === entriesCnt) {
                migrated();
            }
        };

        migrateEntry = function (key, value) {
            _executeNativeMethod(
                function () {
                    localStorage.removeItem(key);
                    entryMigrated();
                },
                function () {
                },
                'store',
                [key, value]
            );
        };

        for (key in localStorage) {
            if (localStorage.hasOwnProperty(key) && key.indexOf('_SS_') === 0) {
                secureStorageData[key] = localStorage.getItem(key);
            }
        }
        entriesCnt = secureStorageData.length;
        if (entriesCnt === 0) {
            migrated();
        }
        for (key in secureStorageData) {
            if (secureStorageData.hasOwnProperty(key)) {
                migrateEntry(key, secureStorageData[key]);
            }
        }
    },

    _migrate_to_native_aes: function (success) {
        var self = this;
        var keysCnt, keysProcessed;
        var keyProcessed;
        var migrated;
        var migrateKey;
        var i;

        migrated = function () {
            _executeNativeMethod(
                function () {
                    success();
                },
                function () {
                },
                'store',
                ['_SS_MIGRATED_TO_NATIVE', '1']
            );
        };

        keyProcessed = function () {
            keysProcessed++;
            if (keysProcessed === keysCnt) {
                migrated();
            }
        };

        migrateKey = function (key) {
            var payload;
            _executeNativeMethod(
                function (value) {
                    payload = JSON.parse(value);
                    if (!payload.native) {
                        self._sjcl_get(
                            function (value) {
                                self._native_set(
                                    function () {
                                        keyProcessed();
                                    },
                                    function () {},
                                    key.replace('_SS_', ''),
                                    value
                                );
                            },
                            function () {},
                            key.replace('_SS_', '')
                        );
                    } else {
                        keyProcessed();
                    }
                },
                function () {
                },
                'fetch',
                [key]
            );
        };

        _executeNativeMethod(
            function (keys) {
                keysProcessed = 0;
                keysCnt = keys.length;
                if (keysCnt === 0) {
                    migrated();
                } else {
                    for (i = 0; i < keysCnt; i++) {
                        migrateKey(keys[i]);
                    }
                }
            },
            function () {
            },
            'keys',
            []
        );
    }
};

SecureStorageBrowser = function (success, error, service) {
    this.service = service;
    setTimeout(success, 0);
    return this;
};

SecureStorageBrowser.prototype = {

    get: function (success, error, key) {
        var value;
        try {
            _checkCallbacks(success, error);
            value = localStorage.getItem('_SS_' + key);
            if (!value) {
                error(new Error('Key "' + key + '" not found.'));
            } else {
                success(value);
            }
        } catch (e) {
            error(e);
        }
    },

    set: function (success, error, key, value) {
        try {
            _checkCallbacks(success, error);
            localStorage.setItem('_SS_' + key, value);
            success(key);
        } catch (e) {
            error(e);
        }
    },
    remove: function (success, error, key) {
        localStorage.removeItem('_SS_' + key);
        success(key);
    }
};

switch (cordova.platformId) {
case 'ios':
    SecureStorage = SecureStorageiOS;
    break;
case 'android':
    SecureStorage = SecureStorageAndroid;
    break;
case 'windows':
    SecureStorage = SecureStorageWindows;
    break;
case 'browser':
    SecureStorage = SecureStorageBrowser;
    break;
default:
    SecureStorage = null;
}

if (!cordova.plugins) {
    cordova.plugins = {};
}

if (!cordova.plugins.SecureStorage) {
    cordova.plugins.SecureStorage = SecureStorage;
}

if (typeof module != 'undefined' && module.exports) {
    module.exports = SecureStorage;
}
<|endoftext|>


<commit_before>const cheerio = require('cheerio')
const normalizeUrl = require('normalize-url')

const { parseFromString, parseFromQuery } = require('./feed')

const request = require('../request')

const normalizeOptions = { removeTrailingSlash: false }

module.exports = async function findFeed({ url, normalize }) {
  const normalizedUrl = normalize ? normalizeUrl(url, normalizeOptions) : url
  let response = null
  let content

  try {
    response = await request(normalizedUrl)
    content = response.text
  } catch (error) {
    if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
      console.log(error) // eslint-disable-line no-console
    }
    return []
  }

  if (
    /application\/(rss|atom)/.test(response.contentType) ||
    /(application|text)\/xml/.test(response.contentType)
  ) {
    try {
      const { title } = await parseFromString({ content })
      return [{ title, link: normalizedUrl }]
    } catch (error) {
      if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
        console.log(error) // eslint-disable-line no-console
      }
    }
  }

  const dom = cheerio.load(response.text)

  if (dom('rss')) {
    try {
      const { title } = await parseFromString({ content })
      return [{ title, link: normalizedUrl }]
    } catch (error) {
      if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
        console.log(error) // eslint-disable-line no-console
      }
    }
  }

  const $linkTags = dom('link[rel="alternate"][type="application/rss+xml"]').add(
    'link[rel="alternate"][type="application/atom+xml"]'
  )

  const urls = $linkTags
    .map((index, $linkTag) => {
      const link = $linkTag.attribs.href
      return normalizeUrl(/^\//.test(link) ? url + link : link, normalizeOptions)
    })
    .toArray()

  const result = (await Promise.all(
    urls.map(async url => {
      try {
        const { title } = await parseFromQuery({ url })
        return { title, link: url }
      } catch (error) {
        if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
          console.log(error) // eslint-disable-line no-console
        }
      }
    })
  )).filter(item => item !== undefined && item !== null)

  if (result.length === 0 && !normalize) {
    return findFeed({ url, normalize: false })
  }

  return result
}
<commit_msg>fix: Only retry feed if normalize is true<commit_after>const cheerio = require('cheerio')
const normalizeUrl = require('normalize-url')

const { parseFromString, parseFromQuery } = require('./feed')

const request = require('../request')

const normalizeOptions = { removeTrailingSlash: false }

module.exports = async function findFeed({ url, normalize }) {
  const normalizedUrl = normalize ? normalizeUrl(url, normalizeOptions) : url
  let response = null
  let content

  try {
    response = await request(normalizedUrl)
    content = response.text
  } catch (error) {
    if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
      console.log(error) // eslint-disable-line no-console
    }
    return []
  }

  if (
    /application\/(rss|atom)/.test(response.contentType) ||
    /(application|text)\/xml/.test(response.contentType)
  ) {
    try {
      const { title } = await parseFromString({ content })
      return [{ title, link: normalizedUrl }]
    } catch (error) {
      if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
        console.log(error) // eslint-disable-line no-console
      }
    }
  }

  const dom = cheerio.load(response.text)

  if (dom('rss')) {
    try {
      const { title } = await parseFromString({ content })
      return [{ title, link: normalizedUrl }]
    } catch (error) {
      if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
        console.log(error) // eslint-disable-line no-console
      }
    }
  }

  const $linkTags = dom('link[rel="alternate"][type="application/rss+xml"]').add(
    'link[rel="alternate"][type="application/atom+xml"]'
  )

  const urls = $linkTags
    .map((index, $linkTag) => {
      const link = $linkTag.attribs.href
      return normalizeUrl(/^\//.test(link) ? url + link : link, normalizeOptions)
    })
    .toArray()

  const result = (await Promise.all(
    urls.map(async url => {
      try {
        const { title } = await parseFromQuery({ url })
        return { title, link: url }
      } catch (error) {
        if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
          console.log(error) // eslint-disable-line no-console
        }
      }
    })
  )).filter(item => item !== undefined && item !== null)

  if (result.length === 0 && normalize) {
    return findFeed({ url, normalize: false })
  }

  return result
}
<|endoftext|>