<commit_before>import { moduleForProperty } from '../../../helpers/properties';
import { create, collection, text, hasClass } from 'ember-cli-page-object';
import withIteratorSymbolDefined from '../../../helpers/with-iterator-symbol-defined';

moduleForProperty('collection', function(test) {
  test('returns the same collection instance', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo, page.foo);
  });

  test('generates a length property', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo.length, 2);
  });

  test('Works with zero length', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `

Lorem

Ipsum

    `);

    assert.equal(page.foo.length, 0);
  });

  test('returns an item', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo.objectAt(0).text, 'Lorem');
    assert.equal(page.foo.objectAt(1).text, 'Ipsum');
  });

  test('collects an array of items', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    let array = page.foo.toArray();
    assert.equal(array.length, 2);
    assert.equal(array[0].text, 'Lorem');
    assert.equal(array[1].text, 'Ipsum');

    let proxyArray = page.foo.toArray();
    assert.equal(proxyArray.length, 2);
    assert.equal(proxyArray[0].text, 'Lorem');
    assert.equal(proxyArray[1].text, 'Ipsum');
  });

  test('produces an iterator for items', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    let textContents = [];
    withIteratorSymbolDefined(() => {
      for (let item of page.foo) {
        textContents.push(item.text);
      }
    });

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('looks for elements inside the scope', async function(assert) {
    let page = create({
      scope: '.scope',

      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem

        Ipsum

    `);

    assert.equal(page.foo.objectAt(0).text, 'Ipsum');
  });

  test('looks for elements inside multiple scopes', async function(assert) {
    let page = create({
      scope: '.scope',

      foo: collection('li', {
        bar: {
          scope: '.another-scope',

          text: text('li', { at: 0 })
        }
      })
    });

    await this.adapter.createTemplate(this, page, `

Blah

Lorem

Ipsum

Dolor

Sit

Amet

    `);

    assert.equal(page.foo.objectAt(1).bar.text, 'Sit');
  });

  test('resets scope for items', async function(assert) {
    let page = create({
      scope: 'div',

      foo: collection('span', {
        resetScope: true,
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem

        Ipsum

    `);

    assert.equal(page.foo.objectAt(0).text, 'Lorem');
  });

  test('sets correct scope to child collections', async function(assert) {
    let page = create({
      scope: '.scope',

      foo: collection('span', {
        bar: collection('em', {
          text: text()
        })
      })
    });

    await this.adapter.createTemplate(this, page, `

Lorem

Ipsum

    `);

    assert.equal(page.foo.objectAt(0).bar.objectAt(0).text, 'Ipsum');
  });

  test('iterates over scoped items with a for loop', async function(assert) {
    let page = create({
      scope: 'div',
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem
        Ipsum

    `);

    let textContents = [];

    for (let i = 0; i < page.foo.length; i++) {
      let item = page.foo.objectAt(i);
      textContents.push(item.text);
    }

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('iterates over scoped items with a for of loop', async function(assert) {
    let page = create({
      scope: 'div',
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem
        Ipsum

    `);

    let textContents = [];

    withIteratorSymbolDefined(() => {
      for (let item of page.foo) {
        textContents.push(item.text);
      }
    });

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('iterates over scoped items with a forEach loop', async function(assert) {
    let page = create({
      scope: 'div',

      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem
        Ipsum

    `);

    let textContents = [];

    page.foo.forEach((item) => {
      textContents.push(item.text);
    });

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('does not mutate definition object', async function(assert) {
    let prop = text('.baz');

    let expected = {
      bar: prop,
      baz: {
        qux: prop
      }
    };

    let actual = {
      bar: prop,
      baz: {
        qux: prop
      }
    };

    let page = create({
      foo: collection('.another-scope', actual)
    });

    await this.adapter.createTemplate(this, page);

    assert.deepEqual(actual, expected);
  });

  test('looks for elements within test container specified', async function(assert) {
    assert.expect(2);

    let expectedContext = '#alternate-ember-testing';
    let page;

    page = create({
      foo: collection('span', {
        testContainer: expectedContext,
      })
    });

    await this.adapter.createTemplate(
      this,
      page,
      'Loremipsum',
      { useAlternateContainer: true }
    );

    assert.equal(page.foo.length, 2);
    assert.equal(page.foo.objectAt(0).text, 'Lorem');
  });

  test('objectAt returns an item', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo.objectAt(0).text, 'Lorem');
    assert.equal(page.foo.objectAt(1).text, 'Ipsum');
  });

  test('forEach works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    let textArray = [];
    page.foo.forEach((i) => {
      textArray.push(i.text);
    });

    assert.deepEqual(textArray, ['Lorem', 'Ipsum']);
  });

  test('map works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.map((i) => i.text), ['Lorem', 'Ipsum']);
  });

  test('mapBy works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.mapBy('text'), ['Lorem', 'Ipsum']);
  });

  test('filter works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        isSpecial: hasClass('special'),
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.filter((i) => i.isSpecial).map((i) => i.text), ['Lorem']);
    assert.deepEqual(page.foo.filter((i) => i.isFoo).map((i) => i.text), []);
  });

  test('filterBy works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        isSpecial: hasClass('special'),
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.filterBy('isSpecial').map((i) => i.text), ['Lorem']);
    assert.deepEqual(page.foo.filterBy('isFoo').map((i) => i.text), []);
  });

  test('uses array accessor', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo[0].text, 'Lorem');
    assert.equal(page.foo[1].text, 'Ipsum');
  });
});
<commit_msg>Ensure collection item is re-used<commit_after>import { moduleForProperty } from '../../../helpers/properties';
import { create, collection, text, hasClass } from 'ember-cli-page-object';
import withIteratorSymbolDefined from '../../../helpers/with-iterator-symbol-defined';

moduleForProperty('collection', function(test) {
  test('returns the same collection instance', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo, page.foo);
  });

  test(`returns the same collection's item instance`, async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo[0], page.foo[0]);
  });

  test('generates a length property', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo.length, 2);
  });

  test('Works with zero length', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `

Lorem

Ipsum

    `);

    assert.equal(page.foo.length, 0);
  });

  test('returns an item', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo.objectAt(0).text, 'Lorem');
    assert.equal(page.foo.objectAt(1).text, 'Ipsum');
  });

  test('collects an array of items', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    let array = page.foo.toArray();
    assert.equal(array.length, 2);
    assert.equal(array[0].text, 'Lorem');
    assert.equal(array[1].text, 'Ipsum');

    let proxyArray = page.foo.toArray();
    assert.equal(proxyArray.length, 2);
    assert.equal(proxyArray[0].text, 'Lorem');
    assert.equal(proxyArray[1].text, 'Ipsum');
  });

  test('produces an iterator for items', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    let textContents = [];
    withIteratorSymbolDefined(() => {
      for (let item of page.foo) {
        textContents.push(item.text);
      }
    });

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('looks for elements inside the scope', async function(assert) {
    let page = create({
      scope: '.scope',

      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem

        Ipsum

    `);

    assert.equal(page.foo.objectAt(0).text, 'Ipsum');
  });

  test('looks for elements inside multiple scopes', async function(assert) {
    let page = create({
      scope: '.scope',

      foo: collection('li', {
        bar: {
          scope: '.another-scope',

          text: text('li', { at: 0 })
        }
      })
    });

    await this.adapter.createTemplate(this, page, `

Blah

Lorem

Ipsum

Dolor

Sit

Amet

    `);

    assert.equal(page.foo.objectAt(1).bar.text, 'Sit');
  });

  test('resets scope for items', async function(assert) {
    let page = create({
      scope: 'div',

      foo: collection('span', {
        resetScope: true,
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem

        Ipsum

    `);

    assert.equal(page.foo.objectAt(0).text, 'Lorem');
  });

  test('sets correct scope to child collections', async function(assert) {
    let page = create({
      scope: '.scope',

      foo: collection('span', {
        bar: collection('em', {
          text: text()
        })
      })
    });

    await this.adapter.createTemplate(this, page, `

Lorem

Ipsum

    `);

    assert.equal(page.foo.objectAt(0).bar.objectAt(0).text, 'Ipsum');
  });

  test('iterates over scoped items with a for loop', async function(assert) {
    let page = create({
      scope: 'div',
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem
        Ipsum

    `);

    let textContents = [];

    for (let i = 0; i < page.foo.length; i++) {
      let item = page.foo.objectAt(i);
      textContents.push(item.text);
    }

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('iterates over scoped items with a for of loop', async function(assert) {
    let page = create({
      scope: 'div',
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem
        Ipsum

    `);

    let textContents = [];

    withIteratorSymbolDefined(() => {
      for (let item of page.foo) {
        textContents.push(item.text);
      }
    });

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('iterates over scoped items with a forEach loop', async function(assert) {
    let page = create({
      scope: 'div',

      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `

        Lorem
        Ipsum

    `);

    let textContents = [];

    page.foo.forEach((item) => {
      textContents.push(item.text);
    });

    assert.deepEqual(textContents, ['Lorem', 'Ipsum']);
  });

  test('does not mutate definition object', async function(assert) {
    let prop = text('.baz');

    let expected = {
      bar: prop,
      baz: {
        qux: prop
      }
    };

    let actual = {
      bar: prop,
      baz: {
        qux: prop
      }
    };

    let page = create({
      foo: collection('.another-scope', actual)
    });

    await this.adapter.createTemplate(this, page);

    assert.deepEqual(actual, expected);
  });

  test('looks for elements within test container specified', async function(assert) {
    assert.expect(2);

    let expectedContext = '#alternate-ember-testing';
    let page;

    page = create({
      foo: collection('span', {
        testContainer: expectedContext,
      })
    });

    await this.adapter.createTemplate(
      this,
      page,
      'Loremipsum',
      { useAlternateContainer: true }
    );

    assert.equal(page.foo.length, 2);
    assert.equal(page.foo.objectAt(0).text, 'Lorem');
  });

  test('objectAt returns an item', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo.objectAt(0).text, 'Lorem');
    assert.equal(page.foo.objectAt(1).text, 'Ipsum');
  });

  test('forEach works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    let textArray = [];
    page.foo.forEach((i) => {
      textArray.push(i.text);
    });

    assert.deepEqual(textArray, ['Lorem', 'Ipsum']);
  });

  test('map works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.map((i) => i.text), ['Lorem', 'Ipsum']);
  });

  test('mapBy works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.mapBy('text'), ['Lorem', 'Ipsum']);
  });

  test('filter works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        isSpecial: hasClass('special'),
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.filter((i) => i.isSpecial).map((i) => i.text), ['Lorem']);
    assert.deepEqual(page.foo.filter((i) => i.isFoo).map((i) => i.text), []);
  });

  test('filterBy works correctly', async function(assert) {
    let page = create({
      foo: collection('span', {
        isSpecial: hasClass('special'),
        text: text()
      })
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.deepEqual(page.foo.filterBy('isSpecial').map((i) => i.text), ['Lorem']);
    assert.deepEqual(page.foo.filterBy('isFoo').map((i) => i.text), []);
  });

  test('uses array accessor', async function(assert) {
    let page = create({
      foo: collection('span')
    });

    await this.adapter.createTemplate(this, page, `
      Lorem
      Ipsum
    `);

    assert.equal(page.foo[0].text, 'Lorem');
    assert.equal(page.foo[1].text, 'Ipsum');
  });
});
<|endoftext|>


<commit_before>/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

let fs = require('fs');
let EventEmitter = require('events');
let helper = require('./helper');

class FrameManager extends EventEmitter {
  /**
   * @param {!Connection} client
   * @param {!Mouse} mouse
   * @return {!Promise<!FrameManager>}
   */
  static async create(client, mouse) {
    let mainFramePayload = await client.send('Page.getResourceTree');
    return new FrameManager(client, mainFramePayload.frameTree, mouse);
  }

  /**
   * @param {!Connection} client
   * @param {!Object} frameTree
   * @param {!Mouse} mouse
   */
  constructor(client, frameTree, mouse) {
    super();
    this._client = client;
    this._mouse = mouse;
    /** @type {!Map<string, !Frame>} */
    this._frames = new Map();
    this._mainFrame = this._addFramesRecursively(null, frameTree);

    this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
    this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame));
    this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId));
    this._client.on('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context));
  }

  /**
   * @return {!Frame}
   */
  mainFrame() {
    return this._mainFrame;
  }

  /**
   * @return {!Array<!Frame>}
   */
  frames() {
    return Array.from(this._frames.values());
  }

  /**
   * @param {string} frameId
   * @param {?string} parentFrameId
   * @return {?Frame}
   */
  _onFrameAttached(frameId, parentFrameId) {
    if (this._frames.has(frameId))
      return;
    console.assert(parentFrameId);
    let parentFrame = this._frames.get(parentFrameId);
    let frame = new Frame(this._client, this._mouse, parentFrame, frameId, null);
    this._frames.set(frame._id, frame);
    this.emit(FrameManager.Events.FrameAttached, frame);
  }

  /**
   * @param {!Object} framePayload
   */
  _onFrameNavigated(framePayload) {
    let frame = this._frames.get(framePayload.id);
    if (!frame) {
      // Simulate missed "frameAttached" for a main frame navigation to the new backend process.
      console.assert(!framePayload.parentId, 'Main frame shouldn\'t have parent frame id.');
      frame = this._mainFrame;
    }
    this._navigateFrame(frame, framePayload.id, framePayload);
  }

  /**
   * @param {string} frameId
   */
  _onFrameDetached(frameId) {
    let frame = this._frames.get(frameId);
    if (frame)
      this._removeFramesRecursively(frame);
  }

  _onExecutionContextCreated(context) {
    const frameId = context.auxData && context.auxData.isDefault ? context.auxData.frameId : null;
    const frame = this._frames.get(frameId);
    if (!frame)
      return;
    frame._defaultContextId = context.id;
  }

  /**
   * @param {!Frame} frame
   * @param {string} newFrameId
   * @param {?Object} newFramePayload
   */
  _navigateFrame(frame, newFrameId, newFramePayload) {
    // Detach all child frames first.
    for (let child of frame.childFrames())
      this._removeFramesRecursively(child);
    this._frames.delete(frame._id, frame);
    frame._id = newFrameId;
    frame._adoptPayload(newFramePayload);
    this._frames.set(newFrameId, frame);
    this.emit(FrameManager.Events.FrameNavigated, frame);
  }

  /**
   * @param {?Frame} parentFrame
   * @param {!Object} frameTreePayload
   * @return {!Frame}
   */
  _addFramesRecursively(parentFrame, frameTreePayload) {
    let framePayload = frameTreePayload.frame;
    let frame = new Frame(this._client, this._mouse, parentFrame, framePayload.id, framePayload);
    this._frames.set(frame._id, frame);

    for (let i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i)
      this._addFramesRecursively(frame, frameTreePayload.childFrames[i]);
    return frame;
  }

  /**
   * @param {!Frame} frame
   */
  _removeFramesRecursively(frame) {
    for (let child of frame.childFrames())
      this._removeFramesRecursively(child);
    frame._detach();
    this._frames.delete(frame._id);
    this.emit(FrameManager.Events.FrameDetached, frame);
  }
}

/** @enum {string} */
FrameManager.Events = {
  FrameAttached: 'frameattached',
  FrameNavigated: 'framenavigated',
  FrameDetached: 'framedetached'
};

/**
 * @unrestricted
 */
class Frame {
  /**
   * @param {!Connection} client
   * @param {!Mouse} mouse
   * @param {?Frame} parentFrame
   * @param {string} frameId
   * @param {?Object} payload
   */
  constructor(client, mouse, parentFrame, frameId, payload) {
    this._client = client;
    this._mouse = mouse;
    this._parentFrame = parentFrame;
    this._url = '';
    this._id = frameId;
    this._defaultContextId = '<not-initialized>';
    /** @type {!Set<!WaitTask>} */
    this._waitTasks = new Set();

    this._adoptPayload(payload);

    /** @type {!Set<!Frame>} */
    this._childFrames = new Set();
    if (this._parentFrame)
      this._parentFrame._childFrames.add(this);
  }

  /**
   * @param {function()} pageFunction
   * @param {!Array<*>} args
   * @return {!Promise<(!Object|undefined)>}
   */
  async evaluate(pageFunction, ...args) {
    return this._evaluateExpression(helper.evaluationString(pageFunction, ...args), true);
  }

  /**
   * @param {string} expression
   * @param {boolean} awaitPromise
   * @return {!Promise<(!Object|undefined)>}
   */
  async _evaluateExpression(expression, awaitPromise) {
    const contextId = this._defaultContextId;
    if (awaitPromise)
      expression = `Promise.resolve(${expression})`;
    let { exceptionDetails, result: remoteObject }  = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise});
    if (exceptionDetails)
      throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
    return await helper.serializeRemoteObject(this._client, remoteObject);
  }

  /**
   * @return {string}
   */
  name() {
    return this._name || '';
  }

  /**
   * @return {string}
   */
  url() {
    return this._url;
  }

  /**
   * @return {?Frame}
   */
  parentFrame() {
    return this._parentFrame;
  }

  /**
   * @return {!Array.<!Frame>}
   */
  childFrames() {
    return Array.from(this._childFrames);
  }

  /**
   * @return {boolean}
   */
  isDetached() {
    return this._detached;
  }

  /**
   * @param {string} filePath
   * @return {!Promise}
   */
  async injectFile(filePath) {
    let contents = await new Promise((resolve, reject) => {
      fs.readFile(filePath, 'utf8', (err, data) => {
        if (err) return reject(err);
        resolve(data);
      });
    });
    contents += `//# sourceURL=` + filePath.replace(/\n/g,'');
    return this._evaluateExpression(contents, false);
  }

  /**
   * @param {string} url
   * @return {!Promise}
   */
  async addScriptTag(url) {
    return this.evaluate(addScriptTag, url);

    /**
     * @param {string} url
     */
    function addScriptTag(url) {
      let script = document.createElement('script');
      script.src = url;
      let promise = new Promise(x => script.onload = x);
      document.head.appendChild(script);
      return promise;
    }
  }

  /**
   * @param {(string|number)} selectorOrTimeout
   * @param {!Object=} options
   * @return {!Promise}
   */
  waitFor(selectorOrTimeout, options = {}) {
    if (typeof selectorOrTimeout === 'string' || selectorOrTimeout instanceof String)
      return this.waitForSelector(selectorOrTimeout, options);
    if (typeof selectorOrTimeout === 'number' || selectorOrTimeout instanceof Number)
      return new Promise(fulfill => setTimeout(fulfill, selectorOrTimeout));
    return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrTimeout)));
  }

  /**
   * @param {string} selector
   * @param {!Object=} options
   * @return {!Promise}
   */
  waitForSelector(selector, options = {}) {
    const timeout = options.timeout || 30000;
    const waitForVisible = !!options.visible;
    const pageScript = helper.evaluationString(waitForSelectorPageFunction, selector, waitForVisible, timeout);
    const waitTask = new WaitTask(this, pageScript, timeout);

    this._waitTasks.add(waitTask);
    let cleanup = () => this._waitTasks.delete(waitTask);
    waitTask.promise.then(cleanup, cleanup);
    return waitTask.promise;
  }

  /**
   * @param {string} selector
   * @param {function(!Element):T} pageFunction
   * @param {!Array<*>} args
   * @return {!Promise<?T>}
   */
  async $(selector, pageFunction, ...args) {
    let argsString = ['node'].concat(args.map(x => JSON.stringify(x))).join(',');
    let expression = `(()=>{
      let node = document.querySelector(${JSON.stringify(selector)});
      if (!node)
        return null;
      return (${pageFunction})(${argsString});
    })()`;
    return this._evaluateExpression(expression, true);
  }

  /**
   * @param {string} selector
   * @param {function(!Element):T} pageFunction
   * @param {!Array<*>} args
   * @return {!Promise<!Array<T>>}
   */
  async $$(selector, pageFunction, ...args) {
    let argsString = ['node, index'].concat(args.map(x => JSON.stringify(x))).join(',');
    let expression = `(()=>{
      let nodes = document.querySelectorAll(${JSON.stringify(selector)});
      return Array.prototype.map.call(nodes, (node, index) => (${pageFunction})(${argsString}));
    })()`;
    return this._evaluateExpression(expression, true);
  }

  /**
   * @return {!Promise<string>}
   */
  async title() {
    return this.evaluate(() =>  document.title);
  }

  /**
   * @param {string} selector
   * @return {!Promise}
   */
  async hover(selector) {
    let center = await this.evaluate(selector => {
      let element = document.querySelector(selector);
      if (!element)
        return null;
      element.scrollIntoViewIfNeeded();
      let rect = element.getBoundingClientRect();
      return {
        x: (Math.max(rect.left, 0) + Math.min(rect.right, window.innerWidth)) / 2,
        y: (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2
      };
    }, selector);
    if (!center)
      throw new Error('No node found for selector: ' + selector);
    await this._mouse.move(center.x, center.y);
  }

  /**
   * @param {string} selector
   * @param {!Object=} options
   * @return {!Promise}
   */
  async click(selector, options) {
    await this.hover(selector);
    await this._mouse.press(options);
    // This is a hack for now, to make clicking less race-prone
    await this.evaluate(() => new Promise(f => requestAnimationFrame(f)));
  }

  /**
   * @param {string} selector
   * @return {!Promise}
   */
  async focus(selector) {
    let success = await this.evaluate(selector => {
      let node = document.querySelector(selector);
      if (!node)
        return false;
      node.focus();
      return true;
    }, selector);
    if (!success)
      throw new Error('No node found for selector: ' + selector);
  }

  /**
   * @param {?Object} framePayload
   */
  _adoptPayload(framePayload) {
    framePayload = framePayload || {
      name: '',
      url: '',
    };
    this._name = framePayload.name;
    this._url = framePayload.url;
    for (let waitTask of this._waitTasks)
      waitTask.run();
  }

  _detach() {
    for (let waitTask of this._waitTasks)
      waitTask.terminate(new Error('waitForSelector failed: frame got detached.'));
    this._detached = true;
    if (this._parentFrame)
      this._parentFrame._childFrames.delete(this);
    this._parentFrame = null;
  }
}
helper.tracePublicAPI(Frame);

class WaitTask {
  /**
   * @param {!Frame} frame
   * @param {string} pageScript
   * @param {number} timeout
   */
  constructor(frame, pageScript, timeout) {
    this._frame = frame;
    this._pageScript = pageScript;
    this._runningTask = null;
    this.promise = new Promise((resolve, reject) => {
      this._resolve = resolve;
      this._reject = reject;
    });
    // Since page navigation requires us to re-install the pageScript, we should track
    // timeout on our end.
    this._timeoutTimer = setTimeout(() => this.terminate(new Error(`waiting failed: timeout ${timeout}ms exceeded`)), timeout);
    this.run();
  }

  /**
   * @param {!Error} error
   */
  terminate(error) {
    this._reject(error);
    this._cleanup();
  }

  run() {
    let runningTask = this._frame._evaluateExpression(this._pageScript, true).then(finish.bind(this), finish.bind(this, false));
    this._runningTask = runningTask;

    /**
     * @param {boolean} success
     * @param {?Error=} error
     */
    function finish(success, error) {
      if (runningTask !== this._runningTask)
        return;
      // Ignore timeouts in pageScript - we track timeouts ourselves.
      if (!success && !error)
        return;
      if (error)
        this._reject(error);
      else
        this._resolve();
      this._cleanup();
    }
  }

  _cleanup() {
    clearTimeout(this._timeoutTimer);
    this._runningTask = null;
  }
}

/**
 * @param {string} selector
 * @param {boolean} waitForVisible
 * @param {number} timeout
 * @return {!Promise<boolean>}
 */
function waitForSelectorPageFunction(selector, visible, timeout) {
  const resultPromise = visible ? waitForVisible(selector) : waitInDOM(selector);
  const timeoutPromise = new Promise(fulfill => setTimeout(fulfill, timeout));
  return Promise.race([
    resultPromise.then(() => true),
    timeoutPromise.then(() => false)
  ]);

  /**
   * @param {string} selector
   * @return {!Promise<!Element>}
   */
  function waitInDOM(selector) {
    let node = document.querySelector(selector);
    if (node)
      return Promise.resolve(node);

    let fulfill;
    const result = new Promise(x => fulfill = x);
    const observer = new MutationObserver(mutations => {
      const node = document.querySelector(selector);
      if (node) {
        observer.disconnect();
        fulfill(node);
      }
    });
    observer.observe(document, {
      childList: true,
      subtree: true
    });
    return result;
  }

  /**
   * @param {string} selector
   * @return {!Promise<!Element>}
   */
  async function waitForVisible(selector) {
    let fulfill;
    const result = new Promise(x => fulfill = x);
    onRaf();
    return result;

    async function onRaf() {
      const node = await waitInDOM(selector);
      const style = window.getComputedStyle(node);
      if (style.display === 'none' || style.visibility === 'hidden') {
        requestAnimationFrame(onRaf);
        return;
      }
      fulfill(node);
    }
  }
}

module.exports = FrameManager;
<commit_msg>Unconditionally update frame payload on navigation only. (#130)<commit_after>/**
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

let fs = require('fs');
let EventEmitter = require('events');
let helper = require('./helper');

class FrameManager extends EventEmitter {
  /**
   * @param {!Connection} client
   * @param {!Mouse} mouse
   * @return {!Promise<!FrameManager>}
   */
  static async create(client, mouse) {
    let mainFramePayload = await client.send('Page.getResourceTree');
    return new FrameManager(client, mainFramePayload.frameTree, mouse);
  }

  /**
   * @param {!Connection} client
   * @param {!Object} frameTree
   * @param {!Mouse} mouse
   */
  constructor(client, frameTree, mouse) {
    super();
    this._client = client;
    this._mouse = mouse;
    /** @type {!Map<string, !Frame>} */
    this._frames = new Map();
    this._mainFrame = this._addFramesRecursively(null, frameTree);

    this._client.on('Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId));
    this._client.on('Page.frameNavigated', event => this._onFrameNavigated(event.frame));
    this._client.on('Page.frameDetached', event => this._onFrameDetached(event.frameId));
    this._client.on('Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context));
  }

  /**
   * @return {!Frame}
   */
  mainFrame() {
    return this._mainFrame;
  }

  /**
   * @return {!Array<!Frame>}
   */
  frames() {
    return Array.from(this._frames.values());
  }

  /**
   * @param {string} frameId
   * @param {?string} parentFrameId
   * @return {?Frame}
   */
  _onFrameAttached(frameId, parentFrameId) {
    if (this._frames.has(frameId))
      return;
    console.assert(parentFrameId);
    let parentFrame = this._frames.get(parentFrameId);
    let frame = new Frame(this._client, this._mouse, parentFrame, frameId);
    this._frames.set(frame._id, frame);
    this.emit(FrameManager.Events.FrameAttached, frame);
  }

  /**
   * @param {!Object} framePayload
   */
  _onFrameNavigated(framePayload) {
    let frame = this._frames.get(framePayload.id);
    if (!frame) {
      // Simulate missed "frameAttached" for a main frame navigation to the new backend process.
      console.assert(!framePayload.parentId, 'Main frame shouldn\'t have parent frame id.');
      frame = this._mainFrame;
    }
    this._navigateFrame(frame, framePayload);
  }

  /**
   * @param {string} frameId
   */
  _onFrameDetached(frameId) {
    let frame = this._frames.get(frameId);
    if (frame)
      this._removeFramesRecursively(frame);
  }

  _onExecutionContextCreated(context) {
    const frameId = context.auxData && context.auxData.isDefault ? context.auxData.frameId : null;
    const frame = this._frames.get(frameId);
    if (!frame)
      return;
    frame._defaultContextId = context.id;
  }

  /**
   * @param {!Frame} frame
   * @param {?Object} newFramePayload
   */
  _navigateFrame(frame, newFramePayload) {
    // Detach all child frames first.
    for (let child of frame.childFrames())
      this._removeFramesRecursively(child);
    this._frames.delete(frame._id, frame);

    // Update frame id to retain frame identity.
    frame._id = newFramePayload.id;

    frame._navigated(newFramePayload);
    this._frames.set(newFramePayload.id, frame);
    this.emit(FrameManager.Events.FrameNavigated, frame);
  }

  /**
   * @param {?Frame} parentFrame
   * @param {!Object} frameTreePayload
   * @return {!Frame}
   */
  _addFramesRecursively(parentFrame, frameTreePayload) {
    let framePayload = frameTreePayload.frame;
    let frame = new Frame(this._client, this._mouse, parentFrame, framePayload.id);
    frame._navigated(framePayload);
    this._frames.set(frame._id, frame);

    for (let i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i)
      this._addFramesRecursively(frame, frameTreePayload.childFrames[i]);
    return frame;
  }

  /**
   * @param {!Frame} frame
   */
  _removeFramesRecursively(frame) {
    for (let child of frame.childFrames())
      this._removeFramesRecursively(child);
    frame._detach();
    this._frames.delete(frame._id);
    this.emit(FrameManager.Events.FrameDetached, frame);
  }
}

/** @enum {string} */
FrameManager.Events = {
  FrameAttached: 'frameattached',
  FrameNavigated: 'framenavigated',
  FrameDetached: 'framedetached'
};

/**
 * @unrestricted
 */
class Frame {
  /**
   * @param {!Connection} client
   * @param {!Mouse} mouse
   * @param {?Frame} parentFrame
   * @param {string} frameId
   */
  constructor(client, mouse, parentFrame, frameId) {
    this._client = client;
    this._mouse = mouse;
    this._parentFrame = parentFrame;
    this._url = '';
    this._id = frameId;
    this._defaultContextId = '<not-initialized>';
    /** @type {!Set<!WaitTask>} */
    this._waitTasks = new Set();

    /** @type {!Set<!Frame>} */
    this._childFrames = new Set();
    if (this._parentFrame)
      this._parentFrame._childFrames.add(this);
  }

  /**
   * @param {function()} pageFunction
   * @param {!Array<*>} args
   * @return {!Promise<(!Object|undefined)>}
   */
  async evaluate(pageFunction, ...args) {
    return this._evaluateExpression(helper.evaluationString(pageFunction, ...args), true);
  }

  /**
   * @param {string} expression
   * @param {boolean} awaitPromise
   * @return {!Promise<(!Object|undefined)>}
   */
  async _evaluateExpression(expression, awaitPromise) {
    const contextId = this._defaultContextId;
    if (awaitPromise)
      expression = `Promise.resolve(${expression})`;
    let { exceptionDetails, result: remoteObject }  = await this._client.send('Runtime.evaluate', { expression, contextId, returnByValue: false, awaitPromise});
    if (exceptionDetails)
      throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
    return await helper.serializeRemoteObject(this._client, remoteObject);
  }

  /**
   * @return {string}
   */
  name() {
    return this._name || '';
  }

  /**
   * @return {string}
   */
  url() {
    return this._url;
  }

  /**
   * @return {?Frame}
   */
  parentFrame() {
    return this._parentFrame;
  }

  /**
   * @return {!Array.<!Frame>}
   */
  childFrames() {
    return Array.from(this._childFrames);
  }

  /**
   * @return {boolean}
   */
  isDetached() {
    return this._detached;
  }

  /**
   * @param {string} filePath
   * @return {!Promise}
   */
  async injectFile(filePath) {
    let contents = await new Promise((resolve, reject) => {
      fs.readFile(filePath, 'utf8', (err, data) => {
        if (err) return reject(err);
        resolve(data);
      });
    });
    contents += `//# sourceURL=` + filePath.replace(/\n/g,'');
    return this._evaluateExpression(contents, false);
  }

  /**
   * @param {string} url
   * @return {!Promise}
   */
  async addScriptTag(url) {
    return this.evaluate(addScriptTag, url);

    /**
     * @param {string} url
     */
    function addScriptTag(url) {
      let script = document.createElement('script');
      script.src = url;
      let promise = new Promise(x => script.onload = x);
      document.head.appendChild(script);
      return promise;
    }
  }

  /**
   * @param {(string|number)} selectorOrTimeout
   * @param {!Object=} options
   * @return {!Promise}
   */
  waitFor(selectorOrTimeout, options = {}) {
    if (typeof selectorOrTimeout === 'string' || selectorOrTimeout instanceof String)
      return this.waitForSelector(selectorOrTimeout, options);
    if (typeof selectorOrTimeout === 'number' || selectorOrTimeout instanceof Number)
      return new Promise(fulfill => setTimeout(fulfill, selectorOrTimeout));
    return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrTimeout)));
  }

  /**
   * @param {string} selector
   * @param {!Object=} options
   * @return {!Promise}
   */
  waitForSelector(selector, options = {}) {
    const timeout = options.timeout || 30000;
    const waitForVisible = !!options.visible;
    const pageScript = helper.evaluationString(waitForSelectorPageFunction, selector, waitForVisible, timeout);
    return new WaitTask(this, pageScript, timeout).promise;
  }

  /**
   * @param {string} selector
   * @param {function(!Element):T} pageFunction
   * @param {!Array<*>} args
   * @return {!Promise<?T>}
   */
  async $(selector, pageFunction, ...args) {
    let argsString = ['node'].concat(args.map(x => JSON.stringify(x))).join(',');
    let expression = `(()=>{
      let node = document.querySelector(${JSON.stringify(selector)});
      if (!node)
        return null;
      return (${pageFunction})(${argsString});
    })()`;
    return this._evaluateExpression(expression, true);
  }

  /**
   * @param {string} selector
   * @param {function(!Element):T} pageFunction
   * @param {!Array<*>} args
   * @return {!Promise<!Array<T>>}
   */
  async $$(selector, pageFunction, ...args) {
    let argsString = ['node, index'].concat(args.map(x => JSON.stringify(x))).join(',');
    let expression = `(()=>{
      let nodes = document.querySelectorAll(${JSON.stringify(selector)});
      return Array.prototype.map.call(nodes, (node, index) => (${pageFunction})(${argsString}));
    })()`;
    return this._evaluateExpression(expression, true);
  }

  /**
   * @return {!Promise<string>}
   */
  async title() {
    return this.evaluate(() =>  document.title);
  }

  /**
   * @param {string} selector
   * @return {!Promise}
   */
  async hover(selector) {
    let center = await this.evaluate(selector => {
      let element = document.querySelector(selector);
      if (!element)
        return null;
      element.scrollIntoViewIfNeeded();
      let rect = element.getBoundingClientRect();
      return {
        x: (Math.max(rect.left, 0) + Math.min(rect.right, window.innerWidth)) / 2,
        y: (Math.max(rect.top, 0) + Math.min(rect.bottom, window.innerHeight)) / 2
      };
    }, selector);
    if (!center)
      throw new Error('No node found for selector: ' + selector);
    await this._mouse.move(center.x, center.y);
  }

  /**
   * @param {string} selector
   * @param {!Object=} options
   * @return {!Promise}
   */
  async click(selector, options) {
    await this.hover(selector);
    await this._mouse.press(options);
    // This is a hack for now, to make clicking less race-prone
    await this.evaluate(() => new Promise(f => requestAnimationFrame(f)));
  }

  /**
   * @param {string} selector
   * @return {!Promise}
   */
  async focus(selector) {
    let success = await this.evaluate(selector => {
      let node = document.querySelector(selector);
      if (!node)
        return false;
      node.focus();
      return true;
    }, selector);
    if (!success)
      throw new Error('No node found for selector: ' + selector);
  }

  /**
   * @param {!Object} framePayload
   */
  _navigated(framePayload) {
    this._name = framePayload.name;
    this._url = framePayload.url;
    for (let waitTask of this._waitTasks)
      waitTask.rerun();
  }

  _detach() {
    for (let waitTask of this._waitTasks)
      waitTask.terminate(new Error('waitForSelector failed: frame got detached.'));
    this._detached = true;
    if (this._parentFrame)
      this._parentFrame._childFrames.delete(this);
    this._parentFrame = null;
  }
}
helper.tracePublicAPI(Frame);

class WaitTask {
  /**
   * @param {!Frame} frame
   * @param {string} pageScript
   * @param {number} timeout
   */
  constructor(frame, pageScript, timeout) {
    this._frame = frame;
    this._pageScript = pageScript;
    this._runningTask = null;
    frame._waitTasks.add(this);
    this.promise = new Promise((resolve, reject) => {
      this._resolve = resolve;
      this._reject = reject;
    });
    // Since page navigation requires us to re-install the pageScript, we should track
    // timeout on our end.
    this._timeoutTimer = setTimeout(() => this.terminate(new Error(`waiting failed: timeout ${timeout}ms exceeded`)), timeout);
    this.rerun();
  }

  /**
   * @param {!Error} error
   */
  terminate(error) {
    this._reject(error);
    this._cleanup();
  }

  rerun() {
    let runningTask = this._frame._evaluateExpression(this._pageScript, true).then(finish.bind(this), finish.bind(this, false));
    this._runningTask = runningTask;

    /**
     * @param {boolean} success
     * @param {?Error=} error
     */
    function finish(success, error) {
      if (runningTask !== this._runningTask)
        return;
      // Ignore timeouts in pageScript - we track timeouts ourselves.
      if (!success && !error)
        return;
      if (error)
        this._reject(error);
      else
        this._resolve();
      this._cleanup();
    }
  }

  _cleanup() {
    clearTimeout(this._timeoutTimer);
    this._frame._waitTasks.delete(this);
    this._runningTask = null;
  }
}

/**
 * @param {string} selector
 * @param {boolean} waitForVisible
 * @param {number} timeout
 * @return {!Promise<boolean>}
 */
function waitForSelectorPageFunction(selector, visible, timeout) {
  const resultPromise = visible ? waitForVisible(selector) : waitInDOM(selector);
  const timeoutPromise = new Promise(fulfill => setTimeout(fulfill, timeout));
  return Promise.race([
    resultPromise.then(() => true),
    timeoutPromise.then(() => false)
  ]);

  /**
   * @param {string} selector
   * @return {!Promise<!Element>}
   */
  function waitInDOM(selector) {
    let node = document.querySelector(selector);
    if (node)
      return Promise.resolve(node);

    let fulfill;
    const result = new Promise(x => fulfill = x);
    const observer = new MutationObserver(mutations => {
      const node = document.querySelector(selector);
      if (node) {
        observer.disconnect();
        fulfill(node);
      }
    });
    observer.observe(document, {
      childList: true,
      subtree: true
    });
    return result;
  }

  /**
   * @param {string} selector
   * @return {!Promise<!Element>}
   */
  async function waitForVisible(selector) {
    let fulfill;
    const result = new Promise(x => fulfill = x);
    onRaf();
    return result;

    async function onRaf() {
      const node = await waitInDOM(selector);
      const style = window.getComputedStyle(node);
      if (style.display === 'none' || style.visibility === 'hidden') {
        requestAnimationFrame(onRaf);
        return;
      }
      fulfill(node);
    }
  }
}

module.exports = FrameManager;
<|endoftext|>


<commit_before>const Discord = require("discord.js");
const rgbHex = require("rgb-hex");
const Skins = require("../data/skins");
const logger = require("../logger");

const filter = (reaction) => {
  return ["👍", "👎"].some((name) => reaction.emoji.name === name);
};

async function postSkin({ md5, title, dest }) {
  const skin = await Skins.getSkinByMd5(md5);
  if (skin == null) {
    console.warn("Could not find skin for md5", { md5, alert: true });
    logger.warn("Could not find skin for md5", { md5, alert: true });
    return;
  }
  const {
    canonicalFilename,
    screenshotUrl,
    skinUrl,
    webampUrl,
    averageColor,
    emails,
    tweetUrl,
    twitterLikes,
    tweetStatus,
    internetArchiveUrl,
    internetArchiveItemName,
    readmeText,
  } = skin;
  title = title ? title(canonicalFilename) : canonicalFilename;

  const embed = new Discord.RichEmbed()
    .setTitle(title)
    .addField("Try Online", `[webamp.org](${webampUrl})`, true)
    .addField("Download", `[${canonicalFilename}](${skinUrl})`, true)
    .addField("Md5", md5, true)
    .setImage(screenshotUrl);

  if (readmeText) {
    // Trim the readme since Discord will reject it otherwise.
    embed.setDescription(`\`\`\`${readmeText.slice(0, 2000)}\`\`\``);
  }
  if (averageColor) {
    try {
      const color = rgbHex(averageColor);
      if (String(color).length === 6) {
        embed.setColor(`#${color}`);
      } else {
        logger.warn("Did not get a safe color", {
          averageColor,
          color,
          warn: true,
        });
      }
    } catch (e) {
      logger.error("Could not use color", { averageColor, alert: true });
    }
  }
  if (emails != null && emails.length) {
    embed.addField("Emails", emails.join(", "), true);
  }
  if (tweetUrl != null) {
    let likes = "";
    if (twitterLikes != null) {
      likes = `(${Number(twitterLikes).toLocaleString()} likes) `;
    }
    embed.addField("Tweet Status", `[Tweeted](${tweetUrl}) ${likes}🐦`, true);
  } else {
    if (tweetStatus === "UNREVIEWED") {
      embed.setFooter("React with 👍 or 👎 to approve or deny");
    }
    embed.addField("Tweet Status", getPrettyTwitterStatus(tweetStatus), true);
  }
  if (internetArchiveUrl) {
    embed.addField(
      "Internet Archive",
      `[${internetArchiveItemName || "Permalink"}](${internetArchiveUrl})`,
      true
    );
  }

  const msg = await dest.send(embed);

  await msg.awaitReactions(filter, { max: 1 }).then(async (collected) => {
    const vote = collected.first();
    const user = vote.users.first();
    switch (vote.emoji.name) {
      case "👍":
        await Skins.approve(md5);
        logger.info(`${user.username} approved ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was approved by ${user.username}`
        );
        msg.react("✅");
        break;
      case "👎":
        await Skins.reject(md5);
        logger.info(`${user.username} rejected ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was rejected by ${user.username}`
        );
        msg.react("❌");
        break;
    }
  });
}

function getPrettyTwitterStatus(status) {
  switch (status) {
    case "APPROVED":
      return "Approved ✅";
    case "REJECTED":
      return "Rejected ❌";
    case "UNREVIEWED":
      return "Unreviewed ❔";
    case "TWEETED":
      return "Tweeted 🐦";
  }
}

module.exports = { postSkin };
<commit_msg>Support reviewing skins using emoji that are surfaced in quick pick on mobile<commit_after>const Discord = require("discord.js");
const rgbHex = require("rgb-hex");
const Skins = require("../data/skins");
const logger = require("../logger");

const filter = (reaction) => {
  return ["👍", "👎", "👏", "😔"].some((name) => reaction.emoji.name === name);
};

async function postSkin({ md5, title, dest }) {
  const skin = await Skins.getSkinByMd5(md5);
  if (skin == null) {
    console.warn("Could not find skin for md5", { md5, alert: true });
    logger.warn("Could not find skin for md5", { md5, alert: true });
    return;
  }
  const {
    canonicalFilename,
    screenshotUrl,
    skinUrl,
    webampUrl,
    averageColor,
    emails,
    tweetUrl,
    twitterLikes,
    tweetStatus,
    internetArchiveUrl,
    internetArchiveItemName,
    readmeText,
  } = skin;
  title = title ? title(canonicalFilename) : canonicalFilename;

  const embed = new Discord.RichEmbed()
    .setTitle(title)
    .addField("Try Online", `[webamp.org](${webampUrl})`, true)
    .addField("Download", `[${canonicalFilename}](${skinUrl})`, true)
    .addField("Md5", md5, true)
    .setImage(screenshotUrl);

  if (readmeText) {
    // Trim the readme since Discord will reject it otherwise.
    embed.setDescription(`\`\`\`${readmeText.slice(0, 2000)}\`\`\``);
  }
  if (averageColor) {
    try {
      const color = rgbHex(averageColor);
      if (String(color).length === 6) {
        embed.setColor(`#${color}`);
      } else {
        logger.warn("Did not get a safe color", {
          averageColor,
          color,
          warn: true,
        });
      }
    } catch (e) {
      logger.error("Could not use color", { averageColor, alert: true });
    }
  }
  if (emails != null && emails.length) {
    embed.addField("Emails", emails.join(", "), true);
  }
  if (tweetUrl != null) {
    let likes = "";
    if (twitterLikes != null) {
      likes = `(${Number(twitterLikes).toLocaleString()} likes) `;
    }
    embed.addField("Tweet Status", `[Tweeted](${tweetUrl}) ${likes}🐦`, true);
  } else {
    if (tweetStatus === "UNREVIEWED") {
      embed.setFooter("React with 👍 or 👎 to approve or deny");
    }
    embed.addField("Tweet Status", getPrettyTwitterStatus(tweetStatus), true);
  }
  if (internetArchiveUrl) {
    embed.addField(
      "Internet Archive",
      `[${internetArchiveItemName || "Permalink"}](${internetArchiveUrl})`,
      true
    );
  }

  const msg = await dest.send(embed);

  await msg.awaitReactions(filter, { max: 1 }).then(async (collected) => {
    const vote = collected.first();
    const user = vote.users.first();
    switch (vote.emoji.name) {
      case "👍":
      case "👏":
        await Skins.approve(md5);
        logger.info(`${user.username} approved ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was approved by ${user.username}`
        );
        msg.react("✅");
        break;
      case "😔":
      case "👎":
        await Skins.reject(md5);
        logger.info(`${user.username} rejected ${md5}`);
        await msg.channel.send(
          `${canonicalFilename} was rejected by ${user.username}`
        );
        msg.react("❌");
        break;
    }
  });
}

function getPrettyTwitterStatus(status) {
  switch (status) {
    case "APPROVED":
      return "Approved ✅";
    case "REJECTED":
      return "Rejected ❌";
    case "UNREVIEWED":
      return "Unreviewed ❔";
    case "TWEETED":
      return "Tweeted 🐦";
  }
}

module.exports = { postSkin };
<|endoftext|>


<commit_before>angular.module('umbraco.security.interceptor')
    // This http interceptor listens for authentication successes and failures
    .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'eventsService', 'requestInterceptorFilter', function ($injector, queue, notifications, eventsService, requestInterceptorFilter) {
        return {

            'request': function(config) {
                // do something on success
                // request for data - if we want to modify headers or url etc
                console.log('request config', config);
                return config;
            },

            'requestError': function(rejection) {
                // do something on error

                console.log('request error rejection', rejection);
                return $q.reject(rejection);
            },

            'response': function(response) {
                // do something on success
                console.log('response success', response);
                var headers = response.headers();
                console.log('response success headers', headers);

                /*
                // Intercept successful requests
                //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it
                //expires. Then we'll update the user in the user service accordingly.
                var headers = originalResponse.headers();
                if (headers["x-umb-user-seconds"]) {
                    // We must use $injector to get the $http service to prevent circular dependency
                    var userService = $injector.get('userService');
                    userService.setUserTimeout(headers["x-umb-user-seconds"]);
                }

                //this checks if the user's values have changed, in which case we need to update the user details throughout
                //the back office similar to how we do when a user logs in
                if (headers["x-umb-user-modified"]) {
                    eventsService.emit("app.userRefresh");
                }
                */

                return response;
            },

            'responseError': function(rejection) {

                console.log('response error rejection', rejection);

                /*
                // Intercept failed requests
                // Make sure we have the configuration of the request (don't we always?)
                var config = originalResponse.config ? originalResponse.config : {};

                // Make sure we have an object for the headers of the request
                var headers = config.headers ? config.headers : {};

                //Here we'll check if we should ignore the error (either based on the original header set or the request configuration)
                if (headers["x-umb-ignore-error"] === "ignore" || config.umbIgnoreErrors === true || (angular.isArray(config.umbIgnoreStatus) && config.umbIgnoreStatus.indexOf(originalResponse.status) !== -1)) {
                    //exit/ignore
                    return promise;
                }
                var filtered = _.find(requestInterceptorFilter(), function (val) {
                    return config.url.indexOf(val) > 0;
                });
                if (filtered) {
                    return promise;
                }

                //A 401 means that the user is not logged in
                if (originalResponse.status === 401 && !originalResponse.config.url.endsWith("umbraco/backoffice/UmbracoApi/Authentication/GetCurrentUser")) {

                    var userService = $injector.get('userService'); // see above

                    //Associate the user name with the retry to ensure we retry for the right user
                    promise = userService.getCurrentUser()
                        .then(function (user) {
                            var userName = user ? user.name : null;
                            //The request bounced because it was not authorized - add a new request to the retry queue
                            return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() {
                                // We must use $injector to get the $http service to prevent circular dependency
                                return $injector.get('$http')(originalResponse.config);
                            });
                        });
                }
                else if (originalResponse.status === 404) {

                    //a 404 indicates that the request was not found - this could be due to a non existing url, or it could
                    //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it

                    var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + "";
                    if (originalResponse.data && originalResponse.data.ExceptionMessage) {
                        errMsg += "
 with error:
" + originalResponse.data.ExceptionMessage + "";
                    }
                    if (originalResponse.config.data) {
                        errMsg += "
 with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
                    }

                    notifications.error(
                        "Request error",
                        errMsg);

                }
                else if (originalResponse.status === 403) {
                    //if the status was a 403 it means the user didn't have permission to do what the request was trying to do.
                    //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was
                    //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper,
                    // or completely custom for services calling resources.

                    //http://issues.umbraco.org/issue/U4-2749

                    //It was decided to just put these messages into the normal status messages.

                    var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + "";
                    if (originalResponse.config.data) {
                        msg += "
 with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
                    }

                    notifications.error(
                        "Authorization error",
                        msg);
                }
                */

                return $q.reject(rejection);
            }
        };
    }])
    //used to set headers on all requests where necessary
    .factory('umbracoRequestInterceptor', function ($q, urlHelper) {
        return {
            //dealing with requests:
            'request': function (config) {
                var queryStrings = urlHelper.getQueryStringParams();
                if (queryStrings.umbDebug === "true" || queryStrings.umbdebug === "true") {
                    config.headers["X-UMB-DEBUG"] = "true";
                }
                return config;
            }
        };
    })
    .value('requestInterceptorFilter', function () {
        return ["www.gravatar.com"];
    })

    // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
    .config(['$httpProvider', function ($httpProvider) {
        $httpProvider.defaults.xsrfHeaderName = 'X-UMB-XSRF-TOKEN';
        $httpProvider.defaults.xsrfCookieName = 'UMB-XSRF-TOKEN';
        // Fixme: https://stackoverflow.com/questions/23804981/alternative-of-httpprovider-responseinterceptors
        $httpProvider.interceptors.push('securityInterceptor');
        $httpProvider.interceptors.push('umbracoRequestInterceptor');
    }]);
<commit_msg>Get the x-umb-user-modified header to work - set a new profile photo or remove it and ensure header is sent down & the event is emitted to update the avatar<commit_after>angular.module('umbraco.security.interceptor')
    // This http interceptor listens for authentication successes and failures
    .factory('securityInterceptor', ['$injector', 'securityRetryQueue', 'notificationsService', 'eventsService', 'requestInterceptorFilter', function ($injector, queue, notifications, eventsService, requestInterceptorFilter) {
        return {

            'request': function(config) {
                // do something on success
                // request for data - if we want to modify headers or url etc
                console.log('request config', config);
                return config;
            },

            'requestError': function(rejection) {
                // do something on error

                console.log('request error rejection', rejection);
                return $q.reject(rejection);
            },

            'response': function(response) {
                // Intercept successful requests
                //Here we'll check if our custom header is in the response which indicates how many seconds the user's session has before it
                //expires. Then we'll update the user in the user service accordingly.
                var headers = response.headers();

                //WB: Logging to help temp-debugging
                console.log('response success', response);
                console.log('response success headers', headers);

                if (headers["x-umb-user-seconds"]) {
                    // We must use $injector to get the $http service to prevent circular dependency
                    var userService = $injector.get('userService');
                    userService.setUserTimeout(headers["x-umb-user-seconds"]);
                }

                //this checks if the user's values have changed, in which case we need to update the user details throughout
                //the back office similar to how we do when a user logs in
                if (headers["x-umb-user-modified"]) {
                    eventsService.emit("app.userRefresh");
                }

                return response;
            },

            'responseError': function(rejection) {

                console.log('response error rejection', rejection);

                /*
                // Intercept failed requests
                // Make sure we have the configuration of the request (don't we always?)
                var config = originalResponse.config ? originalResponse.config : {};

                // Make sure we have an object for the headers of the request
                var headers = config.headers ? config.headers : {};

                //Here we'll check if we should ignore the error (either based on the original header set or the request configuration)
                if (headers["x-umb-ignore-error"] === "ignore" || config.umbIgnoreErrors === true || (angular.isArray(config.umbIgnoreStatus) && config.umbIgnoreStatus.indexOf(originalResponse.status) !== -1)) {
                    //exit/ignore
                    return promise;
                }
                var filtered = _.find(requestInterceptorFilter(), function (val) {
                    return config.url.indexOf(val) > 0;
                });
                if (filtered) {
                    return promise;
                }

                //A 401 means that the user is not logged in
                if (originalResponse.status === 401 && !originalResponse.config.url.endsWith("umbraco/backoffice/UmbracoApi/Authentication/GetCurrentUser")) {

                    var userService = $injector.get('userService'); // see above

                    //Associate the user name with the retry to ensure we retry for the right user
                    promise = userService.getCurrentUser()
                        .then(function (user) {
                            var userName = user ? user.name : null;
                            //The request bounced because it was not authorized - add a new request to the retry queue
                            return queue.pushRetryFn('unauthorized-server', userName, function retryRequest() {
                                // We must use $injector to get the $http service to prevent circular dependency
                                return $injector.get('$http')(originalResponse.config);
                            });
                        });
                }
                else if (originalResponse.status === 404) {

                    //a 404 indicates that the request was not found - this could be due to a non existing url, or it could
                    //be due to accessing a url with a parameter that doesn't exist, either way we should notifiy the user about it

                    var errMsg = "The URL returned a 404 (not found):
" + originalResponse.config.url.split('?')[0] + "";
                    if (originalResponse.data && originalResponse.data.ExceptionMessage) {
                        errMsg += "
 with error:
" + originalResponse.data.ExceptionMessage + "";
                    }
                    if (originalResponse.config.data) {
                        errMsg += "
 with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
                    }

                    notifications.error(
                        "Request error",
                        errMsg);

                }
                else if (originalResponse.status === 403) {
                    //if the status was a 403 it means the user didn't have permission to do what the request was trying to do.
                    //How do we deal with this now, need to tell the user somehow that they don't have permission to do the thing that was
                    //requested. We can either deal with this globally here, or we can deal with it globally for individual requests on the umbRequestHelper,
                    // or completely custom for services calling resources.

                    //http://issues.umbraco.org/issue/U4-2749

                    //It was decided to just put these messages into the normal status messages.

                    var msg = "Unauthorized access to URL:
" + originalResponse.config.url.split('?')[0] + "";
                    if (originalResponse.config.data) {
                        msg += "
 with data:
" + angular.toJson(originalResponse.config.data) + "
Contact your administrator for information.";
                    }

                    notifications.error(
                        "Authorization error",
                        msg);
                }
                */

                return $q.reject(rejection);
            }
        };
    }])
    //used to set headers on all requests where necessary
    .factory('umbracoRequestInterceptor', function ($q, urlHelper) {
        return {
            //dealing with requests:
            'request': function (config) {
                var queryStrings = urlHelper.getQueryStringParams();
                if (queryStrings.umbDebug === "true" || queryStrings.umbdebug === "true") {
                    config.headers["X-UMB-DEBUG"] = "true";
                }
                return config;
            }
        };
    })
    .value('requestInterceptorFilter', function () {
        return ["www.gravatar.com"];
    })

    // We have to add the interceptor to the queue as a string because the interceptor depends upon service instances that are not available in the config block.
    .config(['$httpProvider', function ($httpProvider) {
        $httpProvider.defaults.xsrfHeaderName = 'X-UMB-XSRF-TOKEN';
        $httpProvider.defaults.xsrfCookieName = 'UMB-XSRF-TOKEN';
        // Fixme: https://stackoverflow.com/questions/23804981/alternative-of-httpprovider-responseinterceptors
        $httpProvider.interceptors.push('securityInterceptor');
        $httpProvider.interceptors.push('umbracoRequestInterceptor');
    }]);
<|endoftext|>


<commit_before>~function() {
    function printScene(scene) {
        var active = true;

        var container = document.getElementById("main");
        var div  = document.createElement("div");

        var text = document.createElement("p");
        text.textContent = scene.description;
        var list = document.createElement("ul");

        var links = [];
        scene.links.forEach(function(link) {
            var li = document.createElement("li");
            var a  = document.createElement("a");
            a.href = "#";
            a.textContent = link.text;

            a.addEventListener("click", function(moi) {
                if (active) {
                    active = false;
                    div.className += " inactive";
                    a.className += " chosen";
                }
            }, false);

            li.appendChild(a);
            links.push(a);
            list.appendChild(li);
        });
        div.appendChild(text);
        div.appendChild(list);
        container.appendChild(div);
    }

    var test = {"name":"Homewards.","version":"baobab-0.0.1","scenes":{"home":{"description":"You are at home, sweet home.","title":"Your humble abode.","links":[{"text":"Walk the dinosaur.","condition":0,"actions":0},{"text":"Set fire to the rain.","condition":0,"actions":0}]}},"author":"Art Vandelay","description":"A test story.","start":"home","registers":{"health":10,"owns-dinosaur?":true,"is-raining?":false}}

    window.addEventListener("load", function() {
        printScene(
            test.scenes[test.start]
        );
    }, false);
}();
<commit_msg>orienting those objects<commit_after>~function() {
    function Baobab(game, container) {
        this.game = game;
        this.registers = game.registers;
        this.stack = [];

        this.container = container;
    }
    Baobab.prototype.printScene = function(scene) {
        var active = true;

        var container = this.container;
        var div  = document.createElement("div");

        var text = document.createElement("p");
        text.textContent = scene.description;
        var list = document.createElement("ul");

        var links = [];
        scene.links.forEach(function(link) {
            var li = document.createElement("li");
            var a  = document.createElement("a");
            a.href = "#";
            a.textContent = link.text;

            a.addEventListener("click", function(moi) {
                if (active) {
                    active = false;
                    div.className += " inactive";
                    a.className += " chosen";
                }
            }, false);

            li.appendChild(a);
            links.push(a);
            list.appendChild(li);
        });
        div.appendChild(text);
        div.appendChild(list);
        container.appendChild(div);
    };
    Baobab.prototype.start = function() {
        this.printScene(this.game.scenes[this.game.start]);
    };

    var test = {"name":"Homewards.","version":"baobab-0.0.1","scenes":{"home":{"description":"You are at home, sweet home.","title":"Your humble abode.","links":[{"text":"Walk the dinosaur.","condition":0,"actions":0},{"text":"Set fire to the rain.","condition":0,"actions":0}]}},"author":"Art Vandelay","description":"A test story.","start":"home","registers":{"health":10,"owns-dinosaur?":true,"is-raining?":false}}

    window.addEventListener("load", function() {
        var b = new Baobab(test, document.getElementById("main"));
        b.start();
    }, false);
}();
<|endoftext|>


<commit_before>define("ace/mode/cobol_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;

var CobolHighlightRules = function() {
var keywords = "ACCEPT|MERGE|SUM|ADD||MESSAGE|TABLE|ADVANCING|MODE|TAPE|" +
"AFTER|MULTIPLY|TEST|ALL|NEGATIVE|TEXT|ALPHABET|NEXT|THAN|" +
"ALSO|NO|THEN|ALTERNATE|NOT|THROUGH|AND|NUMBER|THRU|ANY|OCCURS|" +
"TIME|ARE|OF|TO|AREA|OFF|TOP||ASCENDING|OMITTED|TRUE|ASSIGN|ON|TYPE|AT|OPEN|" +
"UNIT|AUTHOR|OR|UNTIL|BEFORE|OTHER|UP|BLANK|OUTPUT|USE|BLOCK|PAGE|USING|BOTTOM|" +
"PERFORM|VALUE|BY|PIC|VALUES|CALL|PICTURE|WHEN|CANCEL|PLUS|WITH|CD|POINTER|WRITE|" +
"CHARACTER|POSITION||ZERO|CLOSE|POSITIVE|ZEROS|COLUMN|PROCEDURE|ZEROES|COMMA|PROGRAM|" +
"COMMON|PROGRAM-ID|COMMUNICATION|QUOTE|COMP|RANDOM|COMPUTE|READ|CONTAINS|RECEIVE|CONFIGURATION|" +
"RECORD|CONTINUE|REDEFINES|CONTROL|REFERENCE|COPY|REMAINDER|COUNT|REPLACE|DATA|REPORT|DATE|RESERVE|" +
"DAY|RESET|DELETE|RETURN|DESTINATION|REWIND|DISABLE|REWRITE|DISPLAY|RIGHT|DIVIDE|RUN|DOWN|SAME|" +
"ELSE|SEARCH|ENABLE|SECTION|END|SELECT|ENVIRONMENT|SENTENCE|EQUAL|SET|ERROR|SIGN|EXIT|SEQUENTIAL|" +
"EXTERNAL|SIZE|FLASE|SORT|FILE|SOURCE|LENGTH|SPACE|LESS|STANDARD|LIMIT|START|LINE|STOP|LOCK|STRING|LOW-VALUE|SUBTRACT";

    var builtinConstants = (
        "true|false|null"
    );

    var builtinFunctions = (
        "count|min|max|avg|sum|rank|now|coalesce|main"
    );

    var keywordMapper = this.createKeywordMapper({
        "support.function": builtinFunctions,
        "keyword": keywords,
        "constant.language": builtinConstants
    }, "identifier", true);

    this.$rules = {
        "start" : [ {
            token : "comment",
            regex : "\\*.*$"
        }, {
            token : "string",           // " string
            regex : '".*?"'
        }, {
            token : "string",           // ' string
            regex : "'.*?'"
        }, {
            token : "constant.numeric", // float
            regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
        }, {
            token : keywordMapper,
            regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
        }, {
            token : "keyword.operator",
            regex : "\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="
        }, {
            token : "paren.lparen",
            regex : "[\\(]"
        }, {
            token : "paren.rparen",
            regex : "[\\)]"
        }, {
            token : "text",
            regex : "\\s+"
        } ]
    };
};

oop.inherits(CobolHighlightRules, TextHighlightRules);

exports.CobolHighlightRules = CobolHighlightRules;
});

define("ace/mode/cobol",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/cobol_highlight_rules","ace/range"], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var CobolHighlightRules = require("./cobol_highlight_rules").CobolHighlightRules;
var Range = require("../range").Range;

var Mode = function() {
    this.HighlightRules = CobolHighlightRules;
};
oop.inherits(Mode, TextMode);

(function() {

    this.lineCommentStart = "*";

    this.$id = "ace/mode/cobol";
}).call(Mode.prototype);

exports.Mode = Mode;

});
<commit_msg>Update tmp/bower_components/ace-builds/src/mode-cobol.js<commit_after><|endoftext|>


<commit_before>/*
 *  base64.js
 *
 *  Licensed under the BSD 3-Clause License.
 *    http://opensource.org/licenses/BSD-3-Clause
 *
 *  References:
 *    http://en.wikipedia.org/wiki/Base64
 */
;(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined'
        ? module.exports = factory(global)
        : typeof define === 'function' && define.amd
        ? define(factory) : factory(global)
}((
    typeof self !== 'undefined' ? self
        : typeof window !== 'undefined' ? window
        : typeof global !== 'undefined' ? global
: this
), function(global) {
    'use strict';
    // existing version for noConflict()
    global = global || {};
    var _Base64 = global.Base64;
    var version = "2.5.1";
    // if node.js and NOT React Native, we use Buffer
    var buffer;
    if (typeof module !== 'undefined' && module.exports) {
        try {
            buffer = eval("require('buffer').Buffer");
        } catch (err) {
            buffer = undefined;
        }
    }
    // constants
    var b64chars
        = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    var b64tab = function(bin) {
        var t = {};
        for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
        return t;
    }(b64chars);
    var fromCharCode = String.fromCharCode;
    // encoder stuff
    var cb_utob = function(c) {
        if (c.length < 2) {
            var cc = c.charCodeAt(0);
            return cc < 0x80 ? c
                : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
                                + fromCharCode(0x80 | (cc & 0x3f)))
                : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
                   + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
                   + fromCharCode(0x80 | ( cc         & 0x3f)));
        } else {
            var cc = 0x10000
                + (c.charCodeAt(0) - 0xD800) * 0x400
                + (c.charCodeAt(1) - 0xDC00);
            return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
                    + fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
                    + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
                    + fromCharCode(0x80 | ( cc         & 0x3f)));
        }
    };
    var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
    var utob = function(u) {
        return u.replace(re_utob, cb_utob);
    };
    var cb_encode = function(ccc) {
        var padlen = [0, 2, 1][ccc.length % 3],
        ord = ccc.charCodeAt(0) << 16
            | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
            | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
        chars = [
            b64chars.charAt( ord >>> 18),
            b64chars.charAt((ord >>> 12) & 63),
            padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
            padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
        ];
        return chars.join('');
    };
    var btoa = global.btoa ? function(b) {
        return global.btoa(b);
    } : function(b) {
        return b.replace(/[\s\S]{1,3}/g, cb_encode);
    };
    var _encode = buffer ?
        buffer.from && Uint8Array && buffer.from !== Uint8Array.from
        ? function (u) {
            return (u.constructor === buffer.constructor ? u : buffer.from(u))
                .toString('base64')
        }
        :  function (u) {
            return (u.constructor === buffer.constructor ? u : new  buffer(u))
                .toString('base64')
        }
        : function (u) { return btoa(utob(u)) }
    ;
    var encode = function(u, urisafe) {
        return !urisafe
            ? _encode(String(u))
            : _encode(String(u)).replace(/[+\/]/g, function(m0) {
                return m0 == '+' ? '-' : '_';
            }).replace(/=/g, '');
    };
    var encodeURI = function(u) { return encode(u, true) };
    // decoder stuff
    var re_btou = new RegExp([
        '[\xC0-\xDF][\x80-\xBF]',
        '[\xE0-\xEF][\x80-\xBF]{2}',
        '[\xF0-\xF7][\x80-\xBF]{3}'
    ].join('|'), 'g');
    var cb_btou = function(cccc) {
        switch(cccc.length) {
        case 4:
            var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
                |    ((0x3f & cccc.charCodeAt(1)) << 12)
                |    ((0x3f & cccc.charCodeAt(2)) <<  6)
                |     (0x3f & cccc.charCodeAt(3)),
            offset = cp - 0x10000;
            return (fromCharCode((offset  >>> 10) + 0xD800)
                    + fromCharCode((offset & 0x3FF) + 0xDC00));
        case 3:
            return fromCharCode(
                ((0x0f & cccc.charCodeAt(0)) << 12)
                    | ((0x3f & cccc.charCodeAt(1)) << 6)
                    |  (0x3f & cccc.charCodeAt(2))
            );
        default:
            return  fromCharCode(
                ((0x1f & cccc.charCodeAt(0)) << 6)
                    |  (0x3f & cccc.charCodeAt(1))
            );
        }
    };
    var btou = function(b) {
        return b.replace(re_btou, cb_btou);
    };
    var cb_decode = function(cccc) {
        var len = cccc.length,
        padlen = len % 4,
        n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
            | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
            | (len > 2 ? b64tab[cccc.charAt(2)] <<  6 : 0)
            | (len > 3 ? b64tab[cccc.charAt(3)]       : 0),
        chars = [
            fromCharCode( n >>> 16),
            fromCharCode((n >>>  8) & 0xff),
            fromCharCode( n         & 0xff)
        ];
        chars.length -= [0, 0, 2, 1][padlen];
        return chars.join('');
    };
    var _atob = global.atob ? function(a) {
        return global.atob(a);
    } : function(a){
        return a.replace(/\S{1,4}/g, cb_decode);
    };
    var atob = function(a) {
        return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
    };
    var _decode = buffer ?
        buffer.from && Uint8Array && buffer.from !== Uint8Array.from
        ? function(a) {
            return (a.constructor === buffer.constructor
                    ? a : buffer.from(a, 'base64')).toString();
        }
        : function(a) {
            return (a.constructor === buffer.constructor
                    ? a : new buffer(a, 'base64')).toString();
        }
        : function(a) { return btou(_atob(a)) };
    var decode = function(a){
        return _decode(
            String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
                .replace(/[^A-Za-z0-9\+\/]/g, '')
        );
    };
    var noConflict = function() {
        var Base64 = global.Base64;
        global.Base64 = _Base64;
        return Base64;
    };
    // export Base64
    global.Base64 = {
        VERSION: version,
        atob: atob,
        btoa: btoa,
        fromBase64: decode,
        toBase64: encode,
        utob: utob,
        encode: encode,
        encodeURI: encodeURI,
        btou: btou,
        decode: decode,
        noConflict: noConflict,
        __buffer__: buffer
    };
    // if ES5 is available, make Base64.extendString() available
    if (typeof Object.defineProperty === 'function') {
        var noEnum = function(v){
            return {value:v,enumerable:false,writable:true,configurable:true};
        };
        global.Base64.extendString = function () {
            Object.defineProperty(
                String.prototype, 'fromBase64', noEnum(function () {
                    return decode(this)
                }));
            Object.defineProperty(
                String.prototype, 'toBase64', noEnum(function (urisafe) {
                    return encode(this, urisafe)
                }));
            Object.defineProperty(
                String.prototype, 'toBase64URI', noEnum(function () {
                    return encode(this, true)
                }));
        };
    }
    //
    // export Base64 to the namespace
    //
    if (global['Meteor']) { // Meteor.js
        Base64 = global.Base64;
    }
    // module.exports and AMD are mutually exclusive.
    // module.exports has precedence.
    if (typeof module !== 'undefined' && module.exports) {
        module.exports.Base64 = global.Base64;
    }
    else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], function(){ return global.Base64 });
    }
    // that's it!
    return {Base64: global.Base64}
}));
<commit_msg>fixed eval<commit_after>/*
 *  base64.js
 *
 *  Licensed under the BSD 3-Clause License.
 *    http://opensource.org/licenses/BSD-3-Clause
 *
 *  References:
 *    http://en.wikipedia.org/wiki/Base64
 */
;(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined'
        ? module.exports = factory(global)
        : typeof define === 'function' && define.amd
        ? define(factory) : factory(global)
}((
    typeof self !== 'undefined' ? self
        : typeof window !== 'undefined' ? window
        : typeof global !== 'undefined' ? global
: this
), function(global) {
    'use strict';
    // existing version for noConflict()
    global = global || {};
    var _Base64 = global.Base64;
    var version = "2.5.1";
    // if node.js and NOT React Native, we use Buffer
    var buffer;
    if (typeof module !== 'undefined' && module.exports && (typeof navigator === "undefined" || navigator.product !== 'ReactNative')) {
        try {
            buffer = require('buffer').Buffer;
        } catch (err) {
            buffer = undefined;
        }
    }
    // constants
    var b64chars
        = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
    var b64tab = function(bin) {
        var t = {};
        for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
        return t;
    }(b64chars);
    var fromCharCode = String.fromCharCode;
    // encoder stuff
    var cb_utob = function(c) {
        if (c.length < 2) {
            var cc = c.charCodeAt(0);
            return cc < 0x80 ? c
                : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
                                + fromCharCode(0x80 | (cc & 0x3f)))
                : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
                   + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
                   + fromCharCode(0x80 | ( cc         & 0x3f)));
        } else {
            var cc = 0x10000
                + (c.charCodeAt(0) - 0xD800) * 0x400
                + (c.charCodeAt(1) - 0xDC00);
            return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
                    + fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
                    + fromCharCode(0x80 | ((cc >>>  6) & 0x3f))
                    + fromCharCode(0x80 | ( cc         & 0x3f)));
        }
    };
    var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
    var utob = function(u) {
        return u.replace(re_utob, cb_utob);
    };
    var cb_encode = function(ccc) {
        var padlen = [0, 2, 1][ccc.length % 3],
        ord = ccc.charCodeAt(0) << 16
            | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
            | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
        chars = [
            b64chars.charAt( ord >>> 18),
            b64chars.charAt((ord >>> 12) & 63),
            padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
            padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
        ];
        return chars.join('');
    };
    var btoa = global.btoa ? function(b) {
        return global.btoa(b);
    } : function(b) {
        return b.replace(/[\s\S]{1,3}/g, cb_encode);
    };
    var _encode = buffer ?
        buffer.from && Uint8Array && buffer.from !== Uint8Array.from
        ? function (u) {
            return (u.constructor === buffer.constructor ? u : buffer.from(u))
                .toString('base64')
        }
        :  function (u) {
            return (u.constructor === buffer.constructor ? u : new  buffer(u))
                .toString('base64')
        }
        : function (u) { return btoa(utob(u)) }
    ;
    var encode = function(u, urisafe) {
        return !urisafe
            ? _encode(String(u))
            : _encode(String(u)).replace(/[+\/]/g, function(m0) {
                return m0 == '+' ? '-' : '_';
            }).replace(/=/g, '');
    };
    var encodeURI = function(u) { return encode(u, true) };
    // decoder stuff
    var re_btou = new RegExp([
        '[\xC0-\xDF][\x80-\xBF]',
        '[\xE0-\xEF][\x80-\xBF]{2}',
        '[\xF0-\xF7][\x80-\xBF]{3}'
    ].join('|'), 'g');
    var cb_btou = function(cccc) {
        switch(cccc.length) {
        case 4:
            var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
                |    ((0x3f & cccc.charCodeAt(1)) << 12)
                |    ((0x3f & cccc.charCodeAt(2)) <<  6)
                |     (0x3f & cccc.charCodeAt(3)),
            offset = cp - 0x10000;
            return (fromCharCode((offset  >>> 10) + 0xD800)
                    + fromCharCode((offset & 0x3FF) + 0xDC00));
        case 3:
            return fromCharCode(
                ((0x0f & cccc.charCodeAt(0)) << 12)
                    | ((0x3f & cccc.charCodeAt(1)) << 6)
                    |  (0x3f & cccc.charCodeAt(2))
            );
        default:
            return  fromCharCode(
                ((0x1f & cccc.charCodeAt(0)) << 6)
                    |  (0x3f & cccc.charCodeAt(1))
            );
        }
    };
    var btou = function(b) {
        return b.replace(re_btou, cb_btou);
    };
    var cb_decode = function(cccc) {
        var len = cccc.length,
        padlen = len % 4,
        n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
            | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
            | (len > 2 ? b64tab[cccc.charAt(2)] <<  6 : 0)
            | (len > 3 ? b64tab[cccc.charAt(3)]       : 0),
        chars = [
            fromCharCode( n >>> 16),
            fromCharCode((n >>>  8) & 0xff),
            fromCharCode( n         & 0xff)
        ];
        chars.length -= [0, 0, 2, 1][padlen];
        return chars.join('');
    };
    var _atob = global.atob ? function(a) {
        return global.atob(a);
    } : function(a){
        return a.replace(/\S{1,4}/g, cb_decode);
    };
    var atob = function(a) {
        return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
    };
    var _decode = buffer ?
        buffer.from && Uint8Array && buffer.from !== Uint8Array.from
        ? function(a) {
            return (a.constructor === buffer.constructor
                    ? a : buffer.from(a, 'base64')).toString();
        }
        : function(a) {
            return (a.constructor === buffer.constructor
                    ? a : new buffer(a, 'base64')).toString();
        }
        : function(a) { return btou(_atob(a)) };
    var decode = function(a){
        return _decode(
            String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
                .replace(/[^A-Za-z0-9\+\/]/g, '')
        );
    };
    var noConflict = function() {
        var Base64 = global.Base64;
        global.Base64 = _Base64;
        return Base64;
    };
    // export Base64
    global.Base64 = {
        VERSION: version,
        atob: atob,
        btoa: btoa,
        fromBase64: decode,
        toBase64: encode,
        utob: utob,
        encode: encode,
        encodeURI: encodeURI,
        btou: btou,
        decode: decode,
        noConflict: noConflict,
        __buffer__: buffer
    };
    // if ES5 is available, make Base64.extendString() available
    if (typeof Object.defineProperty === 'function') {
        var noEnum = function(v){
            return {value:v,enumerable:false,writable:true,configurable:true};
        };
        global.Base64.extendString = function () {
            Object.defineProperty(
                String.prototype, 'fromBase64', noEnum(function () {
                    return decode(this)
                }));
            Object.defineProperty(
                String.prototype, 'toBase64', noEnum(function (urisafe) {
                    return encode(this, urisafe)
                }));
            Object.defineProperty(
                String.prototype, 'toBase64URI', noEnum(function () {
                    return encode(this, true)
                }));
        };
    }
    //
    // export Base64 to the namespace
    //
    if (global['Meteor']) { // Meteor.js
        Base64 = global.Base64;
    }
    // module.exports and AMD are mutually exclusive.
    // module.exports has precedence.
    if (typeof module !== 'undefined' && module.exports) {
        module.exports.Base64 = global.Base64;
    }
    else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], function(){ return global.Base64 });
    }
    // that's it!
    return {Base64: global.Base64}
}));
<|endoftext|>