<commit_before>/*
 * Copyright 2013 The Polymer Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

/**
 * Implements `document.register`
 * @module CustomElements
*/

/**
 * Polyfilled extensions to the `document` object.
 * @class Document
*/

(function(scope) {

// imports

if (!scope) {
  scope = window.CustomElements = {flags:{}};
}
var flags = scope.flags;

// native document.register?

var hasNative = Boolean(document.webkitRegister || document.register);
var useNative = !flags.register && hasNative;

if (useNative) {

  // normalize
  document.register = document.register || document.webkitRegister;

  // stub
  var nop = function() {};

  // exports
  scope.registry = {};
  scope.upgradeElement = nop;

  scope.watchShadow = nop;
  scope.watchAllShadows = nop;
  scope.upgrade = nop;
  scope.upgradeAll = nop;
  scope.upgradeSubtree = nop;
  scope.observeDocument = nop;
  scope.upgradeDocument = nop;
  scope.takeRecords = nop;

} else {

  /**
   * Registers a custom tag name with the document.
   *
   * When a registered element is created, a `readyCallback` method is called
   * in the scope of the element. The `readyCallback` method can be specified on
   * either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking
   * precedence.
   *
   * @method register
   * @param {String} inName The tag name to register. Must include a dash ('-'),
   *    for example 'x-component'.
   * @param {Object} inOptions
   *    @param {String} [inOptions.extends]
   *      (_off spec_) Tag name of an element to extend (or blank for a new
   *      element). This parameter is not part of the specification, but instead
   *      is a hint for the polyfill because the extendee is difficult to infer.
   *      Remember that the input prototype must chain to the extended element's
   *      prototype (or HTMLElement.prototype) regardless of the value of
   *      `extends`.
   *    @param {Object} inOptions.prototype The prototype to use for the new
   *      element. The prototype must inherit from HTMLElement.
   *    @param {Object} [inOptions.lifecycle]
   *      Callbacks that fire at important phases in the life of the custom
   *      element.
   *
   * @example
   *      FancyButton = document.register("fancy-button", {
   *        extends: 'button',
   *        prototype: Object.create(HTMLButtonElement.prototype, {
   *          readyCallback: {
   *            value: function() {
   *              console.log("a fancy-button was created",
   *            }
   *          }
   *        })
   *      });
   * @return {Function} Constructor for the newly registered type.
   */
  function register(inName, inOptions) {
    //console.warn('document.register("' + inName + '", ', inOptions, ')');
    // construct a defintion out of options
    // TODO(sjmiles): probably should clone inOptions instead of mutating it
    var definition = inOptions || {};
    if (!inName) {
      // TODO(sjmiles): replace with more appropriate error (EricB can probably
      // offer guidance)
      throw new Error('Name argument must not be empty');
    }
    // record name
    definition.name = inName;
    // must have a prototype, default to an extension of HTMLElement
    // TODO(sjmiles): probably should throw if no prototype, check spec
    if (!definition.prototype) {
      // TODO(sjmiles): replace with more appropriate error (EricB can probably
      // offer guidance)
      throw new Error('Options missing required prototype property');
    }
    // ensure a lifecycle object so we don't have to null test it
    definition.lifecycle = definition.lifecycle || {};
    // build a list of ancestral custom elements (for native base detection)
    // TODO(sjmiles): we used to need to store this, but current code only
    // uses it in 'resolveTagName': it should probably be inlined
    definition.ancestry = ancestry(definition.extends);
    // extensions of native specializations of HTMLElement require localName
    // to remain native, and use secondary 'is' specifier for extension type
    resolveTagName(definition);
    // some platforms require modifications to the user-supplied prototype
    // chain
    resolvePrototypeChain(definition);
    // overrides to implement attributeChanged callback
    overrideAttributeApi(definition.prototype);
    // 7.1.5: Register the DEFINITION with DOCUMENT
    registerDefinition(inName, definition);
    // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
    // 7.1.8. Return the output of the previous step.
    definition.ctor = generateConstructor(definition);
    definition.ctor.prototype = definition.prototype;
    // force our .constructor to be our actual constructor
    definition.prototype.constructor = definition.ctor;
    // if initial parsing is complete
    if (scope.ready) {
      // upgrade any pre-existing nodes of this type
      scope.upgradeAll(document);
    }
    return definition.ctor;
  }

  function ancestry(inExtends) {
    var extendee = registry[inExtends];
    if (extendee) {
      return ancestry(extendee.extends).concat([extendee]);
    }
    return [];
  }

  function resolveTagName(inDefinition) {
    // if we are explicitly extending something, that thing is our
    // baseTag, unless it represents a custom component
    var baseTag = inDefinition.extends;
    // if our ancestry includes custom components, we only have a
    // baseTag if one of them does
    for (var i=0, a; (a=inDefinition.ancestry[i]); i++) {
      baseTag = a.is && a.tag;
    }
    // our tag is our baseTag, if it exists, and otherwise just our name
    inDefinition.tag = baseTag || inDefinition.name;
    if (baseTag) {
      // if there is a base tag, use secondary 'is' specifier
      inDefinition.is = inDefinition.name;
    }
  }

  function resolvePrototypeChain(inDefinition) {
    // if we don't support __proto__ we need to locate the native level
    // prototype for precise mixing in
    if (!Object.__proto__) {
      // default prototype
      var native = HTMLElement.prototype;
      // work out prototype when using type-extension
      if (inDefinition.is) {
        var inst = document.createElement(inDefinition.tag);
        native = Object.getPrototypeOf(inst);
      }
      // ensure __proto__ reference is installed at each point on the prototype
      // chain.
      // NOTE: On platforms without __proto__, a mixin strategy is used instead
      // of prototype swizzling. In this case, this generated __proto__ provides
      // limited support for prototype traversal.
      var proto = inDefinition.prototype, ancestor;
      while (proto && (proto !== native)) {
        var ancestor = Object.getPrototypeOf(proto);
        proto.__proto__ = ancestor;
        proto = ancestor;
      }
    }
    // cache this in case of mixin
    inDefinition.native = native;
  }

  // SECTION 4

  function instantiate(inDefinition) {
    // 4.a.1. Create a new object that implements PROTOTYPE
    // 4.a.2. Let ELEMENT by this new object
    //
    // the custom element instantiation algorithm must also ensure that the
    // output is a valid DOM element with the proper wrapper in place.
    //
    return upgrade(domCreateElement(inDefinition.tag), inDefinition);
  }

  function upgrade(inElement, inDefinition) {
    // some definitions specify an 'is' attribute
    if (inDefinition.is) {
      inElement.setAttribute('is', inDefinition.is);
    }
    // make 'element' implement inDefinition.prototype
    implement(inElement, inDefinition);
    // flag as upgraded
    inElement.__upgraded__ = true;
    // there should never be a shadow root on inElement at this point
    // we require child nodes be upgraded before `created`
    scope.upgradeSubtree(inElement);
    // lifecycle management
    created(inElement);
    // OUTPUT
    return inElement;
  }

  function implement(inElement, inDefinition) {
    // prototype swizzling is best
    if (Object.__proto__) {
      inElement.__proto__ = inDefinition.prototype;
    } else {
      // where above we can re-acquire inPrototype via
      // getPrototypeOf(Element), we cannot do so when
      // we use mixin, so we install a magic reference
      customMixin(inElement, inDefinition.prototype, inDefinition.native);
      inElement.__proto__ = inDefinition.prototype;
    }
  }

  function customMixin(inTarget, inSrc, inNative) {
    // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
    // any property. This set should be precalculated. We also need to
    // consider this for supporting 'super'.
    var used = {};
    // start with inSrc
    var p = inSrc;
    // sometimes the default is HTMLUnknownElement.prototype instead of
    // HTMLElement.prototype, so we add a test
    // the idea is to avoid mixing in native prototypes, so adding
    // the second test is WLOG
    while (p !== inNative && p !== HTMLUnknownElement.prototype) {
      var keys = Object.getOwnPropertyNames(p);
      for (var i=0, k; k=keys[i]; i++) {
        if (!used[k]) {
          Object.defineProperty(inTarget, k,
              Object.getOwnPropertyDescriptor(p, k));
          used[k] = 1;
        }
      }
      p = Object.getPrototypeOf(p);
    }
  }

  function created(inElement) {
    // invoke createdCallback
    if (inElement.createdCallback) {
      inElement.createdCallback();
    }
  }

  // attribute watching

  function overrideAttributeApi(prototype) {
    // overrides to implement callbacks
    // TODO(sjmiles): should support access via .attributes NamedNodeMap
    // TODO(sjmiles): preserves user defined overrides, if any
    var setAttribute = prototype.setAttribute;
    prototype.setAttribute = function(name, value) {
      changeAttribute.call(this, name, value, setAttribute);
    }
    var removeAttribute = prototype.removeAttribute;
    prototype.removeAttribute = function(name, value) {
      changeAttribute.call(this, name, value, removeAttribute);
    }
  }

  function changeAttribute(name, value, operation) {
    var oldValue = this.getAttribute(name);
    operation.apply(this, arguments);
    if (this.attributeChangedCallback
        && (this.getAttribute(name) !== oldValue)) {
      this.attributeChangedCallback(name, oldValue);
    }
  }

  // element registry (maps tag names to definitions)

  var registry = {};

  function registerDefinition(inName, inDefinition) {
    registry[inName] = inDefinition;
  }

  function generateConstructor(inDefinition) {
    return function() {
      return instantiate(inDefinition);
    };
  }

  function createElement(inTag) {
    var definition = registry[inTag];
    if (definition) {
      return new definition.ctor();
    }
    return domCreateElement(inTag);
  }

  function upgradeElement(inElement) {
    if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) {
      var type = inElement.getAttribute('is') || inElement.localName;
      var definition = registry[type];
      return definition && upgrade(inElement, definition);
    }
  }

  function cloneNode(deep) {
    // call original clone
    var n = domCloneNode.call(this, deep);
    // upgrade the element and subtree
    scope.upgradeAll(n);
    // return the clone
    return n;
  }
  // capture native createElement before we override it

  var domCreateElement = document.createElement.bind(document);

  // capture native cloneNode before we override it

  var domCloneNode = Node.prototype.cloneNode;

  // exports

  document.register = register;
  document.createElement = createElement; // override
  Node.prototype.cloneNode = cloneNode; // override

  scope.registry = registry;

  /**
   * Upgrade an element to a custom element. Upgrading an element
   * causes the custom prototype to be applied, an `is` attribute
   * to be attached (as needed), and invocation of the `readyCallback`.
   * `upgrade` does nothing if the element is already upgraded, or
   * if it matches no registered custom tag name.
   *
   * @method ugprade
   * @param {Element} inElement The element to upgrade.
   * @return {Element} The upgraded element.
   */
  scope.upgrade = upgradeElement;
}

scope.hasNative = hasNative;
scope.useNative = useNative;

})(window.CustomElements);
<commit_msg>support type-extension form of document.createElement<commit_after>/*
 * Copyright 2013 The Polymer Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 */

/**
 * Implements `document.register`
 * @module CustomElements
*/

/**
 * Polyfilled extensions to the `document` object.
 * @class Document
*/

(function(scope) {

// imports

if (!scope) {
  scope = window.CustomElements = {flags:{}};
}
var flags = scope.flags;

// native document.register?

var hasNative = Boolean(document.webkitRegister || document.register);
var useNative = !flags.register && hasNative;

if (useNative) {

  // normalize
  document.register = document.register || document.webkitRegister;

  // stub
  var nop = function() {};

  // exports
  scope.registry = {};
  scope.upgradeElement = nop;

  scope.watchShadow = nop;
  scope.watchAllShadows = nop;
  scope.upgrade = nop;
  scope.upgradeAll = nop;
  scope.upgradeSubtree = nop;
  scope.observeDocument = nop;
  scope.upgradeDocument = nop;
  scope.takeRecords = nop;

} else {

  /**
   * Registers a custom tag name with the document.
   *
   * When a registered element is created, a `readyCallback` method is called
   * in the scope of the element. The `readyCallback` method can be specified on
   * either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking
   * precedence.
   *
   * @method register
   * @param {String} inName The tag name to register. Must include a dash ('-'),
   *    for example 'x-component'.
   * @param {Object} inOptions
   *    @param {String} [inOptions.extends]
   *      (_off spec_) Tag name of an element to extend (or blank for a new
   *      element). This parameter is not part of the specification, but instead
   *      is a hint for the polyfill because the extendee is difficult to infer.
   *      Remember that the input prototype must chain to the extended element's
   *      prototype (or HTMLElement.prototype) regardless of the value of
   *      `extends`.
   *    @param {Object} inOptions.prototype The prototype to use for the new
   *      element. The prototype must inherit from HTMLElement.
   *    @param {Object} [inOptions.lifecycle]
   *      Callbacks that fire at important phases in the life of the custom
   *      element.
   *
   * @example
   *      FancyButton = document.register("fancy-button", {
   *        extends: 'button',
   *        prototype: Object.create(HTMLButtonElement.prototype, {
   *          readyCallback: {
   *            value: function() {
   *              console.log("a fancy-button was created",
   *            }
   *          }
   *        })
   *      });
   * @return {Function} Constructor for the newly registered type.
   */
  function register(inName, inOptions) {
    //console.warn('document.register("' + inName + '", ', inOptions, ')');
    // construct a defintion out of options
    // TODO(sjmiles): probably should clone inOptions instead of mutating it
    var definition = inOptions || {};
    if (!inName) {
      // TODO(sjmiles): replace with more appropriate error (EricB can probably
      // offer guidance)
      throw new Error('Name argument must not be empty');
    }
    // record name
    definition.name = inName;
    // must have a prototype, default to an extension of HTMLElement
    // TODO(sjmiles): probably should throw if no prototype, check spec
    if (!definition.prototype) {
      // TODO(sjmiles): replace with more appropriate error (EricB can probably
      // offer guidance)
      throw new Error('Options missing required prototype property');
    }
    // ensure a lifecycle object so we don't have to null test it
    definition.lifecycle = definition.lifecycle || {};
    // build a list of ancestral custom elements (for native base detection)
    // TODO(sjmiles): we used to need to store this, but current code only
    // uses it in 'resolveTagName': it should probably be inlined
    definition.ancestry = ancestry(definition.extends);
    // extensions of native specializations of HTMLElement require localName
    // to remain native, and use secondary 'is' specifier for extension type
    resolveTagName(definition);
    // some platforms require modifications to the user-supplied prototype
    // chain
    resolvePrototypeChain(definition);
    // overrides to implement attributeChanged callback
    overrideAttributeApi(definition.prototype);
    // 7.1.5: Register the DEFINITION with DOCUMENT
    registerDefinition(inName, definition);
    // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
    // 7.1.8. Return the output of the previous step.
    definition.ctor = generateConstructor(definition);
    definition.ctor.prototype = definition.prototype;
    // force our .constructor to be our actual constructor
    definition.prototype.constructor = definition.ctor;
    // if initial parsing is complete
    if (scope.ready) {
      // upgrade any pre-existing nodes of this type
      scope.upgradeAll(document);
    }
    return definition.ctor;
  }

  function ancestry(inExtends) {
    var extendee = registry[inExtends];
    if (extendee) {
      return ancestry(extendee.extends).concat([extendee]);
    }
    return [];
  }

  function resolveTagName(inDefinition) {
    // if we are explicitly extending something, that thing is our
    // baseTag, unless it represents a custom component
    var baseTag = inDefinition.extends;
    // if our ancestry includes custom components, we only have a
    // baseTag if one of them does
    for (var i=0, a; (a=inDefinition.ancestry[i]); i++) {
      baseTag = a.is && a.tag;
    }
    // our tag is our baseTag, if it exists, and otherwise just our name
    inDefinition.tag = baseTag || inDefinition.name;
    if (baseTag) {
      // if there is a base tag, use secondary 'is' specifier
      inDefinition.is = inDefinition.name;
    }
  }

  function resolvePrototypeChain(inDefinition) {
    // if we don't support __proto__ we need to locate the native level
    // prototype for precise mixing in
    if (!Object.__proto__) {
      // default prototype
      var native = HTMLElement.prototype;
      // work out prototype when using type-extension
      if (inDefinition.is) {
        var inst = document.createElement(inDefinition.tag);
        native = Object.getPrototypeOf(inst);
      }
      // ensure __proto__ reference is installed at each point on the prototype
      // chain.
      // NOTE: On platforms without __proto__, a mixin strategy is used instead
      // of prototype swizzling. In this case, this generated __proto__ provides
      // limited support for prototype traversal.
      var proto = inDefinition.prototype, ancestor;
      while (proto && (proto !== native)) {
        var ancestor = Object.getPrototypeOf(proto);
        proto.__proto__ = ancestor;
        proto = ancestor;
      }
    }
    // cache this in case of mixin
    inDefinition.native = native;
  }

  // SECTION 4

  function instantiate(inDefinition) {
    // 4.a.1. Create a new object that implements PROTOTYPE
    // 4.a.2. Let ELEMENT by this new object
    //
    // the custom element instantiation algorithm must also ensure that the
    // output is a valid DOM element with the proper wrapper in place.
    //
    return upgrade(domCreateElement(inDefinition.tag), inDefinition);
  }

  function upgrade(inElement, inDefinition) {
    // some definitions specify an 'is' attribute
    if (inDefinition.is) {
      inElement.setAttribute('is', inDefinition.is);
    }
    // make 'element' implement inDefinition.prototype
    implement(inElement, inDefinition);
    // flag as upgraded
    inElement.__upgraded__ = true;
    // there should never be a shadow root on inElement at this point
    // we require child nodes be upgraded before `created`
    scope.upgradeSubtree(inElement);
    // lifecycle management
    created(inElement);
    // OUTPUT
    return inElement;
  }

  function implement(inElement, inDefinition) {
    // prototype swizzling is best
    if (Object.__proto__) {
      inElement.__proto__ = inDefinition.prototype;
    } else {
      // where above we can re-acquire inPrototype via
      // getPrototypeOf(Element), we cannot do so when
      // we use mixin, so we install a magic reference
      customMixin(inElement, inDefinition.prototype, inDefinition.native);
      inElement.__proto__ = inDefinition.prototype;
    }
  }

  function customMixin(inTarget, inSrc, inNative) {
    // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
    // any property. This set should be precalculated. We also need to
    // consider this for supporting 'super'.
    var used = {};
    // start with inSrc
    var p = inSrc;
    // sometimes the default is HTMLUnknownElement.prototype instead of
    // HTMLElement.prototype, so we add a test
    // the idea is to avoid mixing in native prototypes, so adding
    // the second test is WLOG
    while (p !== inNative && p !== HTMLUnknownElement.prototype) {
      var keys = Object.getOwnPropertyNames(p);
      for (var i=0, k; k=keys[i]; i++) {
        if (!used[k]) {
          Object.defineProperty(inTarget, k,
              Object.getOwnPropertyDescriptor(p, k));
          used[k] = 1;
        }
      }
      p = Object.getPrototypeOf(p);
    }
  }

  function created(inElement) {
    // invoke createdCallback
    if (inElement.createdCallback) {
      inElement.createdCallback();
    }
  }

  // attribute watching

  function overrideAttributeApi(prototype) {
    // overrides to implement callbacks
    // TODO(sjmiles): should support access via .attributes NamedNodeMap
    // TODO(sjmiles): preserves user defined overrides, if any
    var setAttribute = prototype.setAttribute;
    prototype.setAttribute = function(name, value) {
      changeAttribute.call(this, name, value, setAttribute);
    }
    var removeAttribute = prototype.removeAttribute;
    prototype.removeAttribute = function(name, value) {
      changeAttribute.call(this, name, value, removeAttribute);
    }
  }

  function changeAttribute(name, value, operation) {
    var oldValue = this.getAttribute(name);
    operation.apply(this, arguments);
    if (this.attributeChangedCallback
        && (this.getAttribute(name) !== oldValue)) {
      this.attributeChangedCallback(name, oldValue);
    }
  }

  // element registry (maps tag names to definitions)

  var registry = {};

  function registerDefinition(inName, inDefinition) {
    registry[inName] = inDefinition;
  }

  function generateConstructor(inDefinition) {
    return function() {
      return instantiate(inDefinition);
    };
  }

  function createElement(tag, typeExtension) {
    // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
    // error check it, or perhaps there should only ever be one argument
    var definition = registry[typeExtension || tag];
    if (definition) {
      return new definition.ctor();
    }
    return domCreateElement(tag, typeExtension);
  }

  function upgradeElement(inElement) {
    if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) {
      var type = inElement.getAttribute('is') || inElement.localName;
      var definition = registry[type];
      return definition && upgrade(inElement, definition);
    }
  }

  function cloneNode(deep) {
    // call original clone
    var n = domCloneNode.call(this, deep);
    // upgrade the element and subtree
    scope.upgradeAll(n);
    // return the clone
    return n;
  }
  // capture native createElement before we override it

  var domCreateElement = document.createElement.bind(document);

  // capture native cloneNode before we override it

  var domCloneNode = Node.prototype.cloneNode;

  // exports

  document.register = register;
  document.createElement = createElement; // override
  Node.prototype.cloneNode = cloneNode; // override

  scope.registry = registry;

  /**
   * Upgrade an element to a custom element. Upgrading an element
   * causes the custom prototype to be applied, an `is` attribute
   * to be attached (as needed), and invocation of the `readyCallback`.
   * `upgrade` does nothing if the element is already upgraded, or
   * if it matches no registered custom tag name.
   *
   * @method ugprade
   * @param {Element} inElement The element to upgrade.
   * @return {Element} The upgraded element.
   */
  scope.upgrade = upgradeElement;
}

scope.hasNative = hasNative;
scope.useNative = useNative;

})(window.CustomElements);
<|endoftext|>


<commit_before>/*
Copyright 2020 The Matrix.org Foundation C.I.C.

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

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

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

import { MatrixClientPeg } from './MatrixClientPeg';
import SettingsStore from './settings/SettingsStore';
import * as sdk from './index';
import { _t } from './languageHandler';
import ToastStore from './stores/ToastStore';

const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
const THIS_DEVICE_TOAST_KEY = 'setupencryption';
const OTHER_DEVICES_TOAST_KEY = 'reviewsessions';

function toastKey(deviceId) {
    return "unverified_session_" + deviceId;
}

export default class DeviceListener {
    static sharedInstance() {
        if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener();
        return global.mx_DeviceListener;
    }

    constructor() {
        // device IDs for which the user has dismissed the verify toast ('Later')
        this._dismissed = new Set();
        // has the user dismissed any of the various nag toasts to setup encryption on this device?
        this._dismissedThisDeviceToast = false;

        // cache of the key backup info
        this._keyBackupInfo = null;
        this._keyBackupFetchedAt = null;

        // We keep a list of our own device IDs so we can batch ones that were already
        // there the last time the app launched into a single toast, but display new
        // ones in their own toasts.
        this._ourDeviceIdsAtStart = null;

        // The set of device IDs we're currently displaying toasts for
        this._displayingToastsForDeviceIds = new Set();
    }

    start() {
        MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices);
        MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
        MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
        MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
        MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
        MatrixClientPeg.get().on('accountData', this._onAccountData);
        MatrixClientPeg.get().on('sync', this._onSync);
        this._recheck();
    }

    stop() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices);
            MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
            MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
            MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
            MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
            MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
            MatrixClientPeg.get().removeListener('sync', this._onSync);
        }
        this._dismissed.clear();
        this._dismissedThisDeviceToast = false;
        this._keyBackupInfo = null;
        this._keyBackupFetchedAt = null;
    }

    /**
     * Dismiss notifications about our own unverified devices
     *
     * @param {String[]} deviceIds List of device IDs to dismiss notifications for
     */
    async dismissUnverifiedSessions(deviceIds) {
        for (const d of deviceIds) {
            this._dismissed.add(d);
        }

        this._recheck();
    }

    dismissEncryptionSetup() {
        this._dismissedThisDeviceToast = true;
        this._recheck();
    }

    _ensureDeviceIdsAtStartPopulated() {
        if (this._ourDeviceIdsAtStart === null) {
            const cli = MatrixClientPeg.get();
            this._ourDeviceIdsAtStart = new Set(
                cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId),
            );
        }
    }

    _onWillUpdateDevices = async (users) => {
        const myUserId = MatrixClientPeg.get().getUserId();
        if (users.includes(myUserId)) this._ensureDeviceIdsAtStartPopulated();

        // No need to do a recheck here: we just need to get a snapshot of our devices
        // before we download asny new ones.
    }

    _onDevicesUpdated = (users) => {
        if (!users.includes(MatrixClientPeg.get().getUserId())) return;
        this._recheck();
    }

    _onDeviceVerificationChanged = (userId) => {
        if (userId !== MatrixClientPeg.get().getUserId()) return;
        this._recheck();
    }

    _onUserTrustStatusChanged = (userId, trustLevel) => {
        if (userId !== MatrixClientPeg.get().getUserId()) return;
        this._recheck();
    }

    _onCrossSingingKeysChanged = () => {
        this._recheck();
    }

    _onAccountData = (ev) => {
        // User may have:
        // * migrated SSSS to symmetric
        // * uploaded keys to secret storage
        // * completed secret storage creation
        // which result in account data changes affecting checks below.
        if (
            ev.getType().startsWith('m.secret_storage.') ||
            ev.getType().startsWith('m.cross_signing.')
        ) {
            this._recheck();
        }
    }

    _onSync = (state, prevState) => {
        if (state === 'PREPARED' && prevState === null) this._recheck();
    }

    // The server doesn't tell us when key backup is set up, so we poll
    // & cache the result
    async _getKeyBackupInfo() {
        const now = (new Date()).getTime();
        if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
            this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
            this._keyBackupFetchedAt = now;
        }
        return this._keyBackupInfo;
    }

    async _recheck() {
        const cli = MatrixClientPeg.get();

        if (
            !SettingsStore.getValue("feature_cross_signing") ||
            !await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")
        ) return;

        if (!cli.isCryptoEnabled()) return;
        // don't recheck until the initial sync is complete: lots of account data events will fire
        // while the initial sync is processing and we don't need to recheck on each one of them
        // (we add a listener on sync to do once check after the initial sync is done)
        if (!cli.isInitialSyncComplete()) return;

        const crossSigningReady = await cli.isCrossSigningReady();

        this._ensureDeviceIdsAtStartPopulated();

        if (this._dismissedThisDeviceToast) {
            ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
        } else {
            if (!crossSigningReady) {
                // make sure our keys are finished downlaoding
                await cli.downloadKeys([cli.getUserId()]);
                // cross signing isn't enabled - nag to enable it
                // There are 3 different toasts for:
                if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
                    // Cross-signing on account but this device doesn't trust the master key (verify this session)
                    ToastStore.sharedInstance().addOrReplaceToast({
                        key: THIS_DEVICE_TOAST_KEY,
                        title: _t("Verify this session"),
                        icon: "verification_warning",
                        props: {kind: 'verify_this_session'},
                        component: sdk.getComponent("toasts.SetupEncryptionToast"),
                    });
                } else {
                    const backupInfo = await this._getKeyBackupInfo();
                    if (backupInfo) {
                        // No cross-signing on account but key backup available (upgrade encryption)
                        ToastStore.sharedInstance().addOrReplaceToast({
                            key: THIS_DEVICE_TOAST_KEY,
                            title: _t("Encryption upgrade available"),
                            icon: "verification_warning",
                            props: {kind: 'upgrade_encryption'},
                            component: sdk.getComponent("toasts.SetupEncryptionToast"),
                        });
                    } else {
                        // No cross-signing or key backup on account (set up encryption)
                        ToastStore.sharedInstance().addOrReplaceToast({
                            key: THIS_DEVICE_TOAST_KEY,
                            title: _t("Set up encryption"),
                            icon: "verification_warning",
                            props: {kind: 'set_up_encryption'},
                            component: sdk.getComponent("toasts.SetupEncryptionToast"),
                        });
                    }
                }
                return;
            } else if (await cli.secretStorageKeyNeedsUpgrade()) {
                ToastStore.sharedInstance().addOrReplaceToast({
                    key: THIS_DEVICE_TOAST_KEY,
                    title: _t("Encryption upgrade available"),
                    icon: "verification_warning",
                    props: {kind: 'upgrade_ssss'},
                    component: sdk.getComponent("toasts.SetupEncryptionToast"),
                });
            } else {
                // cross-signing is ready, and we don't need to upgrade encryption
                ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
            }
        }

        // Unverified devices that were there last time the app ran
        // (technically could just be a boolean: we don't actually
        // need to remember the device IDs, but for the sake of
        // symmetry...).
        const oldUnverifiedDeviceIds = new Set();
        // Unverified devices that have appeared since then
        const newUnverifiedDeviceIds = new Set();

        // as long as cross-signing isn't ready,
        // you can't see or dismiss any device toasts
        if (crossSigningReady) {
            const devices = cli.getStoredDevicesForUser(cli.getUserId());
            for (const device of devices) {
                if (device.deviceId == cli.deviceId) continue;

                const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
                if (!deviceTrust.isCrossSigningVerified() && !this._dismissed.has(device.deviceId)) {
                    if (this._ourDeviceIdsAtStart.has(device.deviceId)) {
                        oldUnverifiedDeviceIds.add(device.deviceId);
                    } else {
                        newUnverifiedDeviceIds.add(device.deviceId);
                    }
                }
            }
        }

        // Display or hide the batch toast for old unverified sessions
        if (oldUnverifiedDeviceIds.size > 0) {
            ToastStore.sharedInstance().addOrReplaceToast({
                key: OTHER_DEVICES_TOAST_KEY,
                title: _t("Review where you’re logged in"),
                icon: "verification_warning",
                props: {
                    deviceIds: oldUnverifiedDeviceIds,
                },
                component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"),
            });
        } else {
            ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY);
        }

        // Show toasts for new unverified devices if they aren't already there
        for (const deviceId of newUnverifiedDeviceIds) {
            ToastStore.sharedInstance().addOrReplaceToast({
                key: toastKey(deviceId),
                title: _t("New login. Was this you?"),
                icon: "verification_warning",
                props: { deviceId },
                component: sdk.getComponent("toasts.UnverifiedSessionToast"),
            });
        }

        // ...and hide any we don't need any more
        for (const deviceId of this._displayingToastsForDeviceIds) {
            if (!newUnverifiedDeviceIds.has(deviceId)) {
                ToastStore.sharedInstance().dismissToast(toastKey(deviceId));
            }
        }

        this._displayingToastsForDeviceIds = newUnverifiedDeviceIds;
    }
}
<commit_msg>Comment typo<commit_after>/*
Copyright 2020 The Matrix.org Foundation C.I.C.

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

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

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

import { MatrixClientPeg } from './MatrixClientPeg';
import SettingsStore from './settings/SettingsStore';
import * as sdk from './index';
import { _t } from './languageHandler';
import ToastStore from './stores/ToastStore';

const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
const THIS_DEVICE_TOAST_KEY = 'setupencryption';
const OTHER_DEVICES_TOAST_KEY = 'reviewsessions';

function toastKey(deviceId) {
    return "unverified_session_" + deviceId;
}

export default class DeviceListener {
    static sharedInstance() {
        if (!global.mx_DeviceListener) global.mx_DeviceListener = new DeviceListener();
        return global.mx_DeviceListener;
    }

    constructor() {
        // device IDs for which the user has dismissed the verify toast ('Later')
        this._dismissed = new Set();
        // has the user dismissed any of the various nag toasts to setup encryption on this device?
        this._dismissedThisDeviceToast = false;

        // cache of the key backup info
        this._keyBackupInfo = null;
        this._keyBackupFetchedAt = null;

        // We keep a list of our own device IDs so we can batch ones that were already
        // there the last time the app launched into a single toast, but display new
        // ones in their own toasts.
        this._ourDeviceIdsAtStart = null;

        // The set of device IDs we're currently displaying toasts for
        this._displayingToastsForDeviceIds = new Set();
    }

    start() {
        MatrixClientPeg.get().on('crypto.willUpdateDevices', this._onWillUpdateDevices);
        MatrixClientPeg.get().on('crypto.devicesUpdated', this._onDevicesUpdated);
        MatrixClientPeg.get().on('deviceVerificationChanged', this._onDeviceVerificationChanged);
        MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
        MatrixClientPeg.get().on('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
        MatrixClientPeg.get().on('accountData', this._onAccountData);
        MatrixClientPeg.get().on('sync', this._onSync);
        this._recheck();
    }

    stop() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener('crypto.willUpdateDevices', this._onWillUpdateDevices);
            MatrixClientPeg.get().removeListener('crypto.devicesUpdated', this._onDevicesUpdated);
            MatrixClientPeg.get().removeListener('deviceVerificationChanged', this._onDeviceVerificationChanged);
            MatrixClientPeg.get().removeListener('userTrustStatusChanged', this._onUserTrustStatusChanged);
            MatrixClientPeg.get().removeListener('crossSigning.keysChanged', this._onCrossSingingKeysChanged);
            MatrixClientPeg.get().removeListener('accountData', this._onAccountData);
            MatrixClientPeg.get().removeListener('sync', this._onSync);
        }
        this._dismissed.clear();
        this._dismissedThisDeviceToast = false;
        this._keyBackupInfo = null;
        this._keyBackupFetchedAt = null;
    }

    /**
     * Dismiss notifications about our own unverified devices
     *
     * @param {String[]} deviceIds List of device IDs to dismiss notifications for
     */
    async dismissUnverifiedSessions(deviceIds) {
        for (const d of deviceIds) {
            this._dismissed.add(d);
        }

        this._recheck();
    }

    dismissEncryptionSetup() {
        this._dismissedThisDeviceToast = true;
        this._recheck();
    }

    _ensureDeviceIdsAtStartPopulated() {
        if (this._ourDeviceIdsAtStart === null) {
            const cli = MatrixClientPeg.get();
            this._ourDeviceIdsAtStart = new Set(
                cli.getStoredDevicesForUser(cli.getUserId()).map(d => d.deviceId),
            );
        }
    }

    _onWillUpdateDevices = async (users) => {
        const myUserId = MatrixClientPeg.get().getUserId();
        if (users.includes(myUserId)) this._ensureDeviceIdsAtStartPopulated();

        // No need to do a recheck here: we just need to get a snapshot of our devices
        // before we download any new ones.
    }

    _onDevicesUpdated = (users) => {
        if (!users.includes(MatrixClientPeg.get().getUserId())) return;
        this._recheck();
    }

    _onDeviceVerificationChanged = (userId) => {
        if (userId !== MatrixClientPeg.get().getUserId()) return;
        this._recheck();
    }

    _onUserTrustStatusChanged = (userId, trustLevel) => {
        if (userId !== MatrixClientPeg.get().getUserId()) return;
        this._recheck();
    }

    _onCrossSingingKeysChanged = () => {
        this._recheck();
    }

    _onAccountData = (ev) => {
        // User may have:
        // * migrated SSSS to symmetric
        // * uploaded keys to secret storage
        // * completed secret storage creation
        // which result in account data changes affecting checks below.
        if (
            ev.getType().startsWith('m.secret_storage.') ||
            ev.getType().startsWith('m.cross_signing.')
        ) {
            this._recheck();
        }
    }

    _onSync = (state, prevState) => {
        if (state === 'PREPARED' && prevState === null) this._recheck();
    }

    // The server doesn't tell us when key backup is set up, so we poll
    // & cache the result
    async _getKeyBackupInfo() {
        const now = (new Date()).getTime();
        if (!this._keyBackupInfo || this._keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL) {
            this._keyBackupInfo = await MatrixClientPeg.get().getKeyBackupVersion();
            this._keyBackupFetchedAt = now;
        }
        return this._keyBackupInfo;
    }

    async _recheck() {
        const cli = MatrixClientPeg.get();

        if (
            !SettingsStore.getValue("feature_cross_signing") ||
            !await cli.doesServerSupportUnstableFeature("org.matrix.e2e_cross_signing")
        ) return;

        if (!cli.isCryptoEnabled()) return;
        // don't recheck until the initial sync is complete: lots of account data events will fire
        // while the initial sync is processing and we don't need to recheck on each one of them
        // (we add a listener on sync to do once check after the initial sync is done)
        if (!cli.isInitialSyncComplete()) return;

        const crossSigningReady = await cli.isCrossSigningReady();

        this._ensureDeviceIdsAtStartPopulated();

        if (this._dismissedThisDeviceToast) {
            ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
        } else {
            if (!crossSigningReady) {
                // make sure our keys are finished downlaoding
                await cli.downloadKeys([cli.getUserId()]);
                // cross signing isn't enabled - nag to enable it
                // There are 3 different toasts for:
                if (cli.getStoredCrossSigningForUser(cli.getUserId())) {
                    // Cross-signing on account but this device doesn't trust the master key (verify this session)
                    ToastStore.sharedInstance().addOrReplaceToast({
                        key: THIS_DEVICE_TOAST_KEY,
                        title: _t("Verify this session"),
                        icon: "verification_warning",
                        props: {kind: 'verify_this_session'},
                        component: sdk.getComponent("toasts.SetupEncryptionToast"),
                    });
                } else {
                    const backupInfo = await this._getKeyBackupInfo();
                    if (backupInfo) {
                        // No cross-signing on account but key backup available (upgrade encryption)
                        ToastStore.sharedInstance().addOrReplaceToast({
                            key: THIS_DEVICE_TOAST_KEY,
                            title: _t("Encryption upgrade available"),
                            icon: "verification_warning",
                            props: {kind: 'upgrade_encryption'},
                            component: sdk.getComponent("toasts.SetupEncryptionToast"),
                        });
                    } else {
                        // No cross-signing or key backup on account (set up encryption)
                        ToastStore.sharedInstance().addOrReplaceToast({
                            key: THIS_DEVICE_TOAST_KEY,
                            title: _t("Set up encryption"),
                            icon: "verification_warning",
                            props: {kind: 'set_up_encryption'},
                            component: sdk.getComponent("toasts.SetupEncryptionToast"),
                        });
                    }
                }
                return;
            } else if (await cli.secretStorageKeyNeedsUpgrade()) {
                ToastStore.sharedInstance().addOrReplaceToast({
                    key: THIS_DEVICE_TOAST_KEY,
                    title: _t("Encryption upgrade available"),
                    icon: "verification_warning",
                    props: {kind: 'upgrade_ssss'},
                    component: sdk.getComponent("toasts.SetupEncryptionToast"),
                });
            } else {
                // cross-signing is ready, and we don't need to upgrade encryption
                ToastStore.sharedInstance().dismissToast(THIS_DEVICE_TOAST_KEY);
            }
        }

        // Unverified devices that were there last time the app ran
        // (technically could just be a boolean: we don't actually
        // need to remember the device IDs, but for the sake of
        // symmetry...).
        const oldUnverifiedDeviceIds = new Set();
        // Unverified devices that have appeared since then
        const newUnverifiedDeviceIds = new Set();

        // as long as cross-signing isn't ready,
        // you can't see or dismiss any device toasts
        if (crossSigningReady) {
            const devices = cli.getStoredDevicesForUser(cli.getUserId());
            for (const device of devices) {
                if (device.deviceId == cli.deviceId) continue;

                const deviceTrust = await cli.checkDeviceTrust(cli.getUserId(), device.deviceId);
                if (!deviceTrust.isCrossSigningVerified() && !this._dismissed.has(device.deviceId)) {
                    if (this._ourDeviceIdsAtStart.has(device.deviceId)) {
                        oldUnverifiedDeviceIds.add(device.deviceId);
                    } else {
                        newUnverifiedDeviceIds.add(device.deviceId);
                    }
                }
            }
        }

        // Display or hide the batch toast for old unverified sessions
        if (oldUnverifiedDeviceIds.size > 0) {
            ToastStore.sharedInstance().addOrReplaceToast({
                key: OTHER_DEVICES_TOAST_KEY,
                title: _t("Review where you’re logged in"),
                icon: "verification_warning",
                props: {
                    deviceIds: oldUnverifiedDeviceIds,
                },
                component: sdk.getComponent("toasts.BulkUnverifiedSessionsToast"),
            });
        } else {
            ToastStore.sharedInstance().dismissToast(OTHER_DEVICES_TOAST_KEY);
        }

        // Show toasts for new unverified devices if they aren't already there
        for (const deviceId of newUnverifiedDeviceIds) {
            ToastStore.sharedInstance().addOrReplaceToast({
                key: toastKey(deviceId),
                title: _t("New login. Was this you?"),
                icon: "verification_warning",
                props: { deviceId },
                component: sdk.getComponent("toasts.UnverifiedSessionToast"),
            });
        }

        // ...and hide any we don't need any more
        for (const deviceId of this._displayingToastsForDeviceIds) {
            if (!newUnverifiedDeviceIds.has(deviceId)) {
                ToastStore.sharedInstance().dismissToast(toastKey(deviceId));
            }
        }

        this._displayingToastsForDeviceIds = newUnverifiedDeviceIds;
    }
}
<|endoftext|>


<commit_before>/**
 * @class 弹出浮动层 alert
 * @classdesc:类说明:
 * @extends
 * @example: 举例
 *
  var _alert=Ds.alert('测试你儿童团是的发生的',{
   // title:'提示',
   btns:'确定,取消',
   hasClose:true,
   bgClose:true,
   //color:'#00f',
   //hasBg:false,
  });
  //直接代码操作关闭这个alert
  _alert.Hide();
  //直接代码操作 重新显示
  _alert.Show('新的提示内容');
  //这个方法可以关闭所有alert
  Ds.ui.PopLayer.closeAllAlert();
 *
 * @author: maksim email:maksim.lin@foxmail.com
 * @copyright: Ds是累积平时项目工作的经验代码库，不属于职位任务与项目的内容。里面代码大部分理念来至曾经flash 前端时代，尽力减小类之间耦合，通过webpack按需request使用。Ds库里内容多来至网络与参考其他开源代码库。Ds库也开源开放，随意使用在所属的职位任务与项目中。
 * @constructor
 **/
(function(factory) {
  var root = (typeof self == 'object' && self.self == self && self) ||
    (typeof global == 'object' && global.global == global && global);

  if (typeof define === 'function' && define.amd) {
    define(['exports'], function(exports) {
      module.exports = factory(root, exports);
    });
  } else if (typeof exports !== 'undefined') {
    module.exports = factory(root, exports);
  } else {
    factory(root, {});
  }
}(function(root, modelObj) {
  root.Ds = root.Ds || {};
  root.Ds.ui = root.Ds.ui || {};
  root.Ds.ui.PopLayer = {};

  root.Ds.ui.PopLayer.PopAlert = PopAlert;

  //开发过程使用这个html结构
  // _PopLayerDom=$(require('./popLayer/PopLayer.html'));
  //弹出框的dom结构创建  基于./popLayer/PopLayer.html基础上 进行html与css内容的压缩 在线工具 http://tool.oschina.net/jscompress?type=2 与 http://www.css88.com/tool/html2js/
  var _PopLayerDom = $(['

</spa>

<svg class=\'closeSvg\' style=\'position: absolute;top:25%;left:25%;width: 50%;height: 50%;\' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.8 14.8"><path d="M8.2 7.4L14.8.7 14 0 7.6 6.7.7 0 .2.7l6.8 6.7L0 14l.7.8L7.5 8l6.7 6.8.7-.7z" data-name="Layer0 0 FILL"/></svg>

'].join(""));
  //抽取css插入到html内
  var _Style = _PopLayerDom.find('style');
  $('body').append(_Style);
  //抽取提示框dom结构备用
  _AlertDom = $(_PopLayerDom.find('.Ds_PopLayer')[0]);
  console.log(_AlertDom);
  // alert 弹出框列表
  var _AlertList = [];
  /**
   * alert 弹出框 类
   * @param {[String]} info   [提示内容]
   * @param {[String]} title  [提示标题]
   * @param {[Object]} config [description]
   */
  function PopAlert(info, config) {
    var _Self = this;
    //提示内容
    var _Info = info || '';
    var _Title, //头部
      _BtnArr, //按钮文字内容
      _BgClose, //背景是否关闭
      _OkFun, //确定事件
      _NoFun, //取消事件
      _CloseFun; //关闭按钮，如果没事件 那就用取消

    //dom元素创建
    var _View = _AlertDom.clone();
    Object.defineProperty(this, "View", {
      get: function() {
        return _View;
      },
    });

    var _TitleDom = _View.find('.title');
    var _InftoDom = _View.find('.info span');
    var _OkDom = _View.find('.btnPanel .btn0');
    var _NoDom = _View.find('.btnPanel .btn1');
    var _CloseDom = _View.find('.close');
    var _BgDom = _View.find('.bg');
    //交互事件
    _BgDom.on('click', function() {
      if (!_BgClose) return;
      _Self.Hide();
      if (_CloseFun) _CloseFun();
    });
    _CloseDom.on('click', function() {
      _Self.Hide();
      if (_CloseFun) _CloseFun();
    });
    _OkDom.on('click', function() {
      _Self.Hide();
      if (_OkFun) _OkFun();
    });
    _NoDom.on('click', function() {
      _Self.Hide();
      if (_NoFun) _NoFun();
    });
    //初始化配置
    InitConfig(config);
    /**
     * 初始化配置
     * @param {[Object]} config [配置参数]
     * config.title 提示框标题 默认'' 会不出现标题
     * config.btns 按钮文案 可以是string 或者 Array,如：不需要按钮'' 单个按钮:'确定' ['确定']  双按钮：'确定,取消' ['确定','取消']
     * config.okFun 确定执行方法  默认空
     * config.noFun 取消执行方法  默认空
     * config.closeFun 关闭执行方法  默认使用config.noFun代替 如果有背景关闭与关闭按钮下会执行
     * config.hasClose 是否有关闭按钮  默认无关闭按钮
     * config.bgClose 是否支持背景关闭  默认不支持
     * config.color 字体颜色  默认#000;
     * config.panelColor 框颜色  默认#fff;
     * config.bgColor 框颜色  默认#000;
     * config.bgAlpha 框颜色  默认0.8;
     * config.hasBg 是否有背景遮罩  默认true;
     */
    function InitConfig(config) {
      //初始化配置
      var _config = config || {};
      //头部
      _Title = _config.title || '';
      //确定事件
      _OkFun = _config.okFun || null;
      //取消事件
      _NoFun = _config.noFun || null;
      //关闭按钮，如果没事件 那就用取消
      _CloseFun = _config.closeFun || _NoFun;
      //按钮
      var _btns = _config.btns !== undefined ? _config.btns : '确定';
      if (typeof(_btns) == 'string') {
        if (_btns.indexOf(',') != -1) {
          _BtnArr = _btns.split(',');
        } else {
          _BtnArr = [_btns];
        }
      } else if (_btns instanceof Array) {
        _BtnArr = _btns;
      } else {
        _BtnArr = [];
      }
      if (_BtnArr.length <= 0) {
        _OkDom.hide();
        _NoDom.hide();
      } else if (_BtnArr.length === 1) {
        _OkDom.show();
        _NoDom.hide();
        _OkDom.html(_BtnArr[0]);
        _OkDom.css({
          'borderRight': '0px solid #ccc',
        });

      } else {
        _OkDom.html(_BtnArr[0]);
        _OkDom.css({
          'borderRight': '1px solid #ccc',
        });
        _OkDom.show();
        _NoDom.html(_BtnArr[1]);
        _NoDom.show();
      }
      var _hasClose = _config.hasClose !== undefined ? _config.hasClose : false;
      // console.log('_hasClose', _hasClose);
      if (_hasClose) {
        _CloseDom.show();
      } else {
        _CloseDom.hide();
      }
      _BgClose = _config.bgClose || false;
      //框颜色
      if (_config.panelColor) {
        _View.find('.panel').css({
          background: _config.panelColor
        });
        _View.find('.close').css({
          background: _config.panelColor
        });
      }
      //背景颜色
      if (_config.bgColor) {
        _View.find('.bg').css({
          background: _config.bgColor
        });
      }
      //背景透明度
      if (_config.bgAlpha) {
        _View.find('.bg').css({
          opacity: _config.bgAlpha
        });
      }
      //字体颜色
      if (_config.color) {
        _View.find('.title').css({
          color: _config.color
        });
        _View.find('.info').css({
          color: _config.color
        });
        _View.find('.btn').css({
          color: _config.color
        });
        _View.find('.close svg').css({
          fill: _config.color
        });
      }
      //是否拥有背景遮罩
      var _hasBg = config.hasBg !== undefined ? config.hasBg : true;
      if (_hasBg) {
        _BgDom.show();
      } else {
        _BgDom.hide();
      }
    }
    /**
     * 显示提示框
     * @param  {[type]} info  [description]
     * @return {[type]}       [description]
     */
    this.Show = function(info, config) {
      if (config) InitConfig(config);
      _Info = info || _Info;
      _TitleDom.html(_Title);
      _InftoDom.html(_Info);
      $('body').append(_View);
      //弹出框索引
      if (_AlertList.indexOf(_Self) == -1) {
        _AlertList.push(_Self);
      }
    };
    /**
     * 隐藏当前提示框
     * @return {[type]} [description]
     */
    this.Hide = function() {
      _View.remove();
      //从显示的alert列表中进行做删除
      for (var i = 0; i < _AlertList.length; i++) {
        var _temp = _AlertList[i];
        if (_temp == _Self) {
          _AlertList.splice(i, 1);
        }
      }
    };
  }

  /**
   * 快捷弹出提示框
   * @param  {[String]} info   [提示内容]
   * @param  {[Object]} config [提示框配置]
   * @return {[PopAlert]}  result   [这个alert框对象 ]
   */
  root.Ds.ui.PopLayer.alert = function(info, config) {
    var _AlertPanel = new PopAlert(info, config);
    _AlertPanel.Show();
    return _AlertPanel;
  };
  /**
   * 关闭所有提示框
   * @return {[type]} [description]
   */
  root.Ds.ui.PopLayer.closeAllAlert = function() {
    var _tempArr = _AlertList;
    _AlertList = [];
    for (var i = 0; i < _tempArr.length; i++) {
      var _alert = _tempArr[i];
      _alert.Hide();
    }
  };
  //开放给顶层命名空间下
  root.Ds.alert = root.Ds.ui.PopLayer.alert;
  root.Ds.closeAllAlert = root.Ds.ui.PopLayer.closeAllAlert;

  return root.Ds.ui.PopLayer;
}));
<commit_msg>update<commit_after>/**
 * @class 弹出浮动层 alert
 * @classdesc:类说明:
 * @extends
 * @example: 举例
 *
  var _alert=Ds.alert('测试你儿童团是的发生的',{
   // title:'提示',
   btns:'确定,取消',
   hasClose:true,
   bgClose:true,
   //color:'#00f',
   //hasBg:false,
  });
  //直接代码操作关闭这个alert
  _alert.Hide();
  //直接代码操作 重新显示
  _alert.Show('新的提示内容');
  //这个方法可以关闭所有alert
  Ds.ui.PopLayer.closeAllAlert();
 *
 * @author: maksim email:maksim.lin@foxmail.com
 * @copyright: Ds是累积平时项目工作的经验代码库，不属于职位任务与项目的内容。里面代码大部分理念来至曾经flash 前端时代，尽力减小类之间耦合，通过webpack按需request使用。Ds库里内容多来至网络与参考其他开源代码库。Ds库也开源开放，随意使用在所属的职位任务与项目中。
 * @constructor
 **/
(function(factory) {
  var root = (typeof self == 'object' && self.self == self && self) ||
    (typeof global == 'object' && global.global == global && global);

  if (typeof define === 'function' && define.amd) {
    define(['exports'], function(exports) {
      module.exports = factory(root, exports);
    });
  } else if (typeof exports !== 'undefined') {
    module.exports = factory(root, exports);
  } else {
    factory(root, {});
  }
}(function(root, modelObj) {
  root.Ds = root.Ds || {};
  root.Ds.ui = root.Ds.ui || {};
  root.Ds.ui.PopLayer = {};

  root.Ds.ui.PopLayer.PopAlert = PopAlert;

  //开发过程使用这个html结构
  // _PopLayerDom=$(require('./popLayer/PopLayer.html'));
  //弹出框的dom结构创建  基于./popLayer/PopLayer.html基础上 进行html与css内容的压缩 在线工具 http://tool.oschina.net/jscompress?type=2 与 http://www.css88.com/tool/html2js/
  var _PopLayerDom = $(['

</spa>

<svg class=\'closeSvg\' style=\'position: absolute;top:25%;left:25%;width: 50%;height: 50%;\' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14.8 14.8"><path d="M8.2 7.4L14.8.7 14 0 7.6 6.7.7 0 .2.7l6.8 6.7L0 14l.7.8L7.5 8l6.7 6.8.7-.7z" data-name="Layer0 0 FILL"/></svg>

'].join(""));
  //抽取css插入到html内
  var _Style = _PopLayerDom.find('style');
  $('body').append(_Style);
  //抽取提示框dom结构备用
  _AlertDom = $(_PopLayerDom.find('.Ds_PopLayer')[0]);
  console.log(_AlertDom);
  // alert 弹出框列表
  var _AlertList = [];
  /**
   * alert 弹出框 类
   * @param {[String]} info   [提示内容]
   * @param {[String]} title  [提示标题]
   * @param {[Object]} config [description]
   */
  function PopAlert(info, config) {
    var _Self = this;
    //提示内容
    var _Info = info || '';
    var _Title, //头部
      _BtnArr, //按钮文字内容
      _BgClose, //背景是否关闭
      _OkFun, //确定事件
      _NoFun, //取消事件
      _CloseFun; //关闭按钮，如果没事件 那就用取消

    //dom元素创建
    var _View = _AlertDom.clone();
    Object.defineProperty(this, "View", {
      get: function() {
        return _View;
      },
    });

    var _TitleDom = _View.find('.title');
    var _InftoDom = _View.find('.info span');
    var _OkDom = _View.find('.btnPanel .btn0');
    var _NoDom = _View.find('.btnPanel .btn1');
    var _CloseDom = _View.find('.close');
    var _BgDom = _View.find('.bg');
    //交互事件
    _BgDom.on('click', function() {
      if (!_BgClose) return;
      _Self.Hide();
      if (_CloseFun) _CloseFun();
    });
    _CloseDom.on('click', function() {
      _Self.Hide();
      if (_CloseFun) _CloseFun();
    });
    _OkDom.on('click', function() {
      _Self.Hide();
      if (_OkFun) _OkFun();
    });
    _NoDom.on('click', function() {
      _Self.Hide();
      if (_NoFun) _NoFun();
    });
    //初始化配置
    InitConfig(config);
    /**
     * 初始化配置
     * @param {[Object]} config [配置参数]
     * config.title 提示框标题 默认'' 会不出现标题
     * config.btns 按钮文案 可以是string 或者 Array,如：不需要按钮'' 单个按钮:'确定' ['确定']  双按钮：'确定,取消' ['确定','取消']
     * config.ok 确定执行方法  默认空
     * config.no 取消执行方法  默认空
     * config.close 关闭执行方法  默认使用config.noFun代替 如果有背景关闭与关闭按钮下会执行
     * config.hasClose 是否有关闭按钮  默认无关闭按钮
     * config.bgClose 是否支持背景关闭  默认不支持
     * config.color 字体颜色  默认#000;
     * config.panelColor 框颜色  默认#fff;
     * config.bgColor 框颜色  默认#000;
     * config.bgAlpha 框颜色  默认0.8;
     * config.hasBg 是否有背景遮罩  默认true;
     */
    function InitConfig(config) {
      //初始化配置
      var _config = config || {};
      //头部
      _Title = _config.title || '';
      //确定事件
      _OkFun = _config.ok || null;
      //取消事件
      _NoFun = _config.no || null;
      //关闭按钮，如果没事件 那就用取消
      _CloseFun = _config.close || _NoFun;
      //按钮
      var _btns = _config.btns !== undefined ? _config.btns : '确定';
      if (typeof(_btns) == 'string') {
        if (_btns.indexOf(',') != -1) {
          _BtnArr = _btns.split(',');
        } else {
          _BtnArr = [_btns];
        }
      } else if (_btns instanceof Array) {
        _BtnArr = _btns;
      } else {
        _BtnArr = [];
      }
      if (_BtnArr.length <= 0) {
        _OkDom.hide();
        _NoDom.hide();
      } else if (_BtnArr.length === 1) {
        _OkDom.show();
        _NoDom.hide();
        _OkDom.html(_BtnArr[0]);
        _OkDom.css({
          'borderRight': '0px solid #ccc',
        });

      } else {
        _OkDom.html(_BtnArr[0]);
        _OkDom.css({
          'borderRight': '1px solid #ccc',
        });
        _OkDom.show();
        _NoDom.html(_BtnArr[1]);
        _NoDom.show();
      }
      var _hasClose = _config.hasClose !== undefined ? _config.hasClose : false;
      // console.log('_hasClose', _hasClose);
      if (_hasClose) {
        _CloseDom.show();
      } else {
        _CloseDom.hide();
      }
      _BgClose = _config.bgClose || false;
      //框颜色
      if (_config.panelColor) {
        _View.find('.panel').css({
          background: _config.panelColor
        });
        _View.find('.close').css({
          background: _config.panelColor
        });
      }
      //背景颜色
      if (_config.bgColor) {
        _View.find('.bg').css({
          background: _config.bgColor
        });
      }
      //背景透明度
      if (_config.bgAlpha) {
        _View.find('.bg').css({
          opacity: _config.bgAlpha
        });
      }
      //字体颜色
      if (_config.color) {
        _View.find('.title').css({
          color: _config.color
        });
        _View.find('.info').css({
          color: _config.color
        });
        _View.find('.btn').css({
          color: _config.color
        });
        _View.find('.close svg').css({
          fill: _config.color
        });
      }
      //是否拥有背景遮罩
      var _hasBg = _config.hasBg!== undefined?  _config.hasBg:true ;
      if (_hasBg) {
        _BgDom.show();
      } else {
        _BgDom.hide();
      }
    }
    /**
     * 显示提示框
     * @param  {[type]} info  [description]
     * @return {[type]}       [description]
     */
    this.Show = function(info, config) {
      if (config) InitConfig(config);
      _Info = info || _Info;
      _TitleDom.html(_Title);
      _InftoDom.html(_Info);
      $('body').append(_View);
      //弹出框索引
      if (_AlertList.indexOf(_Self) == -1) {
        _AlertList.push(_Self);
      }
    };
    /**
     * 隐藏当前提示框
     * @return {[type]} [description]
     */
    this.Hide = function() {
      _View.remove();
      //从显示的alert列表中进行做删除
      for (var i = 0; i < _AlertList.length; i++) {
        var _temp = _AlertList[i];
        if (_temp == _Self) {
          _AlertList.splice(i, 1);
        }
      }
    };
  }

  /**
   * 快捷弹出提示框
   * @param  {[String]} info   [提示内容]
   * @param  {[Object]} config [提示框配置]
   * @return {[PopAlert]}  result   [这个alert框对象 ]
   */
  root.Ds.ui.PopLayer.alert = function(info, config) {
    var _AlertPanel = new PopAlert(info, config);
    _AlertPanel.Show();
    return _AlertPanel;
  };
  /**
   * 关闭所有提示框
   * @return {[type]} [description]
   */
  root.Ds.ui.PopLayer.closeAllAlert = function() {
    var _tempArr = _AlertList;
    _AlertList = [];
    for (var i = 0; i < _tempArr.length; i++) {
      var _alert = _tempArr[i];
      _alert.Hide();
    }
  };
  //开放给顶层命名空间下
  root.Ds.alert = root.Ds.ui.PopLayer.alert;
  root.Ds.closeAllAlert = root.Ds.ui.PopLayer.closeAllAlert;

  return root.Ds.ui.PopLayer;
}));
<|endoftext|>


<commit_before>(function(self) {
'use strict';

var appearsBrowserified = typeof self !== 'undefined' &&
                          typeof process !== 'undefined' &&
                          Object.prototype.toString.call(process) === '[object Object]';

var RouteRecognizer = appearsBrowserified ? require('route-recognizer') : self.RouteRecognizer;
var FakeXMLHttpRequest = appearsBrowserified ? require('fake-xml-http-request') : self.FakeXMLHttpRequest;

/**
 * parseURL - decompose a URL into its parts
 * @param  {String} url a URL
 * @return {Object} parts of the URL, including the following
 *
 * 'https://www.yahoo.com:1234/mypage?test=yes#abc'
 *
 * {
 *   host: 'www.yahoo.com:1234',
 *   protocol: 'https:',
 *   search: '?test=yes',
 *   hash: '#abc',
 *   href: 'https://www.yahoo.com:1234/mypage?test=yes#abc',
 *   pathname: '/mypage',
 *   fullpath: '/mypage?test=yes'
 * }
 */
function parseURL(url) {
  // TODO: something for when document isn't present... #yolo
  var anchor = document.createElement('a');
  anchor.href = url;
  anchor.fullpath = anchor.pathname + (anchor.search || '') + (anchor.hash || '');
  return anchor;
}

/**
 * Registry
 *
 * A registry is a map of HTTP verbs to route recognizers.
 */

function Registry(/* host */) {
  // Herein we keep track of RouteRecognizer instances
  // keyed by HTTP method. Feel free to add more as needed.
  this.verbs = {
    GET: new RouteRecognizer(),
    PUT: new RouteRecognizer(),
    POST: new RouteRecognizer(),
    DELETE: new RouteRecognizer(),
    PATCH: new RouteRecognizer(),
    HEAD: new RouteRecognizer(),
    OPTIONS: new RouteRecognizer()
  };
}

/**
 * Hosts
 *
 * a map of hosts to Registries, ultimately allowing
 * a per-host-and-port, per HTTP verb lookup of RouteRecognizers
 */
function Hosts() {
  this._registries = {};
}

/**
 * Hosts#forURL - retrieve a map of HTTP verbs to RouteRecognizers
 *                for a given URL
 *
 * @param  {String} url a URL
 * @return {Registry}   a map of HTTP verbs to RouteRecognizers
 *                      corresponding to the provided URL's
 *                      hostname and port
 */
Hosts.prototype.forURL = function(url) {
  var host = parseURL(url).host;
  var registry = this._registries[host];

  if (registry === undefined) {
    registry = (this._registries[host] = new Registry(host));
  }

  return registry.verbs;
};

function Pretender(/* routeMap1, routeMap2, ...*/) {
  this.hosts = new Hosts();

  this.handlers = [];
  this.handledRequests = [];
  this.passthroughRequests = [];
  this.unhandledRequests = [];
  this.requestReferences = [];

  // reference the native XMLHttpRequest object so
  // it can be restored later
  this._nativeXMLHttpRequest = self.XMLHttpRequest;

  // capture xhr requests, channeling them into
  // the route map.
  self.XMLHttpRequest = interceptor(this);

  // 'start' the server
  this.running = true;

  // trigger the route map DSL.
  for (var i = 0; i < arguments.length; i++) {
    this.map(arguments[i]);
  }
}

function interceptor(pretender) {
  function FakeRequest() {
    // super()
    FakeXMLHttpRequest.call(this);
  }
  // extend
  var proto = new FakeXMLHttpRequest();
  proto.send = function send() {
    if (!pretender.running) {
      throw new Error('You shut down a Pretender instance while there was a pending request. ' +
            'That request just tried to complete. Check to see if you accidentally shut down ' +
            'a pretender earlier than you intended to');
    }

    FakeXMLHttpRequest.prototype.send.apply(this, arguments);
    if (!pretender.checkPassthrough(this)) {
      pretender.handleRequest(this);
    } else {
      var xhr = createPassthrough(this);
      xhr.send.apply(xhr, arguments);
    }
  };

  function createPassthrough(fakeXHR) {
    // event types to handle on the xhr
    var evts = ['error', 'timeout', 'abort', 'readystatechange'];

    // event types to handle on the xhr.upload
    var uploadEvents = ['progress'];

    // properties to copy from the native xhr to fake xhr
    var lifecycleProps = ['readyState', 'responseText', 'responseXML', 'status', 'statusText'];

    var xhr = fakeXHR._passthroughRequest = new pretender._nativeXMLHttpRequest();

    if (fakeXHR.responseType === 'arraybuffer') {
      lifecycleProps = ['readyState', 'response', 'status', 'statusText'];
      xhr.responseType = fakeXHR.responseType;
    }

    // Use onload if the browser supports it
    if ('onload' in xhr) {
      evts.push('load');
    }

    // add progress event for async calls
    if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') {
      evts.push('progress');
    }

    // update `propertyNames` properties from `fromXHR` to `toXHR`
    function copyLifecycleProperties(propertyNames, fromXHR, toXHR) {
      for (var i = 0; i < propertyNames.length; i++) {
        var prop = propertyNames[i];
        if (fromXHR[prop]) {
          toXHR[prop] = fromXHR[prop];
        }
      }
    }

    // fire fake event on `eventable`
    function dispatchEvent(eventable, eventType, event) {
      eventable.dispatchEvent(event);
      if (eventable['on' + eventType]) {
        eventable['on' + eventType](event);
      }
    }

    // set the on- handler on the native xhr for the given eventType
    function createHandler(eventType) {
      xhr['on' + eventType] = function(event) {
        copyLifecycleProperties(lifecycleProps, xhr, fakeXHR);
        dispatchEvent(fakeXHR, eventType, event);
      };
    }

    // set the on- handler on the native xhr's `upload` property for
    // the given eventType
    function createUploadHandler(eventType) {
      if (xhr.upload) {
        xhr.upload['on' + eventType] = function(event) {
          dispatchEvent(fakeXHR.upload, eventType, event);
        };
      }
    }

    xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password);

    var i;
    for (i = 0; i < evts.length; i++) {
      createHandler(evts[i]);
    }
    for (i = 0; i < uploadEvents.length; i++) {
      createUploadHandler(uploadEvents[i]);
    }

    if (fakeXHR.async) {
      xhr.timeout = fakeXHR.timeout;
      xhr.withCredentials = fakeXHR.withCredentials;
    }
    for (var h in fakeXHR.requestHeaders) {
      xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]);
    }
    return xhr;
  }

  proto._passthroughCheck = function(method, args) {
    if (this._passthroughRequest) {
      return this._passthroughRequest[method].apply(this._passthroughRequest, args);
    }
    return FakeXMLHttpRequest.prototype[method].apply(this, args);
  };

  proto.abort = function abort() {
    return this._passthroughCheck('abort', arguments);
  };

  proto.getResponseHeader = function getResponseHeader() {
    return this._passthroughCheck('getResponseHeader', arguments);
  };

  proto.getAllResponseHeaders = function getAllResponseHeaders() {
    return this._passthroughCheck('getAllResponseHeaders', arguments);
  };

  FakeRequest.prototype = proto;
  return FakeRequest;
}

function verbify(verb) {
  return function(path, handler, async) {
    this.register(verb, path, handler, async);
  };
}

function scheduleProgressEvent(request, startTime, totalTime) {
  setTimeout(function() {
    if (!request.aborted && !request.status) {
      var ellapsedTime = new Date().getTime() - startTime.getTime();
      request.upload._progress(true, ellapsedTime, totalTime);
      request._progress(true, ellapsedTime, totalTime);
      scheduleProgressEvent(request, startTime, totalTime);
    }
  }, 50);
}

function isArray(array) {
  return Object.prototype.toString.call(array) === '[object Array]';
}

var PASSTHROUGH = {};

Pretender.prototype = {
  get: verbify('GET'),
  post: verbify('POST'),
  put: verbify('PUT'),
  'delete': verbify('DELETE'),
  patch: verbify('PATCH'),
  head: verbify('HEAD'),
  map: function(maps) {
    maps.call(this);
  },
  register: function register(verb, url, handler, async) {
    if (!handler) {
      throw new Error('The function you tried passing to Pretender to handle ' +
        verb + ' ' + url + ' is undefined or missing.');
    }

    handler.numberOfCalls = 0;
    handler.async = async;
    this.handlers.push(handler);

    var registry = this.hosts.forURL(url)[verb];

    registry.add([{
      path: parseURL(url).fullpath,
      handler: handler
    }]);
  },
  passthrough: PASSTHROUGH,
  checkPassthrough: function checkPassthrough(request) {
    var verb = request.method.toUpperCase();

    var path = parseURL(request.url).fullpath;

    verb = verb.toUpperCase();

    var recognized = this.hosts.forURL(request.url)[verb].recognize(path);
    var match = recognized && recognized[0];
    if (match && match.handler === PASSTHROUGH) {
      this.passthroughRequests.push(request);
      this.passthroughRequest(verb, path, request);
      return true;
    }

    return false;
  },
  handleRequest: function handleRequest(request) {
    var verb = request.method.toUpperCase();
    var path = request.url;

    var handler = this._handlerFor(verb, path, request);

    if (handler) {
      handler.handler.numberOfCalls++;
      var async = handler.handler.async;
      this.handledRequests.push(request);

      try {
        var statusHeadersAndBody = handler.handler(request);
        if (!isArray(statusHeadersAndBody)) {
          var note = 'Remember to `return [status, headers, body];` in your route handler.';
          throw new Error('Nothing returned by handler for ' + path + '. ' + note);
        }

        var status = statusHeadersAndBody[0],
            headers = this.prepareHeaders(statusHeadersAndBody[1]),
            body = this.prepareBody(statusHeadersAndBody[2], headers),
            pretender = this;

        this.handleResponse(request, async, function() {
          request.respond(status, headers, body);
          pretender.handledRequest(verb, path, request);
        });
      } catch (error) {
        this.erroredRequest(verb, path, request, error);
        this.resolve(request);
      }
    } else {
      this.unhandledRequests.push(request);
      this.unhandledRequest(verb, path, request);
    }
  },
  handleResponse: function handleResponse(request, strategy, callback) {
    var delay = typeof strategy === 'function' ? strategy() : strategy;
    delay = typeof delay === 'boolean' || typeof delay === 'number' ? delay : 0;

    if (delay === false) {
      callback();
    } else {
      var pretender = this;
      pretender.requestReferences.push({
        request: request,
        callback: callback
      });

      if (delay !== true) {
        scheduleProgressEvent(request, new Date(), delay);
        setTimeout(function() {
          pretender.resolve(request);
        }, delay);
      }
    }
  },
  resolve: function resolve(request) {
    for (var i = 0, len = this.requestReferences.length; i < len; i++) {
      var res = this.requestReferences[i];
      if (res.request === request) {
        res.callback();
        this.requestReferences.splice(i, 1);
        break;
      }
    }
  },
  requiresManualResolution: function(verb, path) {
    var handler = this._handlerFor(verb.toUpperCase(), path, {});
    if (!handler) { return false; }

    var async = handler.handler.async;
    return typeof async === 'function' ? async() === true : async === true;
  },
  prepareBody: function(body) { return body; },
  prepareHeaders: function(headers) { return headers; },
  handledRequest: function(/* verb, path, request */) { /* no-op */},
  passthroughRequest: function(/* verb, path, request */) { /* no-op */},
  unhandledRequest: function(verb, path/*, request */) {
    throw new Error('Pretender intercepted ' + verb + ' ' +
      path + ' but no handler was defined for this type of request');
  },
  erroredRequest: function(verb, path, request, error) {
    error.message = 'Pretender intercepted ' + verb + ' ' +
      path + ' but encountered an error: ' + error.message;
    throw error;
  },
  _handlerFor: function(verb, url, request) {
    var registry = this.hosts.forURL(url)[verb];
    var matches = registry.recognize(parseURL(url).fullpath);

    var match = matches ? matches[0] : null;
    if (match) {
      request.params = match.params;
      request.queryParams = matches.queryParams;
    }

    return match;
  },
  shutdown: function shutdown() {
    self.XMLHttpRequest = this._nativeXMLHttpRequest;

    // 'stop' the server
    this.running = false;
  }
};

Pretender.parseURL = parseURL;
Pretender.Hosts = Hosts;
Pretender.Registry = Registry;

if (typeof module === 'object') {
  module.exports = Pretender;
} else if (typeof define !== 'undefined') {
  define('pretender', [], function() {
    return Pretender;
  });
}
self.Pretender = Pretender;
}(self));
<commit_msg>Add options verbify.<commit_after>(function(self) {
'use strict';

var appearsBrowserified = typeof self !== 'undefined' &&
                          typeof process !== 'undefined' &&
                          Object.prototype.toString.call(process) === '[object Object]';

var RouteRecognizer = appearsBrowserified ? require('route-recognizer') : self.RouteRecognizer;
var FakeXMLHttpRequest = appearsBrowserified ? require('fake-xml-http-request') : self.FakeXMLHttpRequest;

/**
 * parseURL - decompose a URL into its parts
 * @param  {String} url a URL
 * @return {Object} parts of the URL, including the following
 *
 * 'https://www.yahoo.com:1234/mypage?test=yes#abc'
 *
 * {
 *   host: 'www.yahoo.com:1234',
 *   protocol: 'https:',
 *   search: '?test=yes',
 *   hash: '#abc',
 *   href: 'https://www.yahoo.com:1234/mypage?test=yes#abc',
 *   pathname: '/mypage',
 *   fullpath: '/mypage?test=yes'
 * }
 */
function parseURL(url) {
  // TODO: something for when document isn't present... #yolo
  var anchor = document.createElement('a');
  anchor.href = url;
  anchor.fullpath = anchor.pathname + (anchor.search || '') + (anchor.hash || '');
  return anchor;
}

/**
 * Registry
 *
 * A registry is a map of HTTP verbs to route recognizers.
 */

function Registry(/* host */) {
  // Herein we keep track of RouteRecognizer instances
  // keyed by HTTP method. Feel free to add more as needed.
  this.verbs = {
    GET: new RouteRecognizer(),
    PUT: new RouteRecognizer(),
    POST: new RouteRecognizer(),
    DELETE: new RouteRecognizer(),
    PATCH: new RouteRecognizer(),
    HEAD: new RouteRecognizer(),
    OPTIONS: new RouteRecognizer()
  };
}

/**
 * Hosts
 *
 * a map of hosts to Registries, ultimately allowing
 * a per-host-and-port, per HTTP verb lookup of RouteRecognizers
 */
function Hosts() {
  this._registries = {};
}

/**
 * Hosts#forURL - retrieve a map of HTTP verbs to RouteRecognizers
 *                for a given URL
 *
 * @param  {String} url a URL
 * @return {Registry}   a map of HTTP verbs to RouteRecognizers
 *                      corresponding to the provided URL's
 *                      hostname and port
 */
Hosts.prototype.forURL = function(url) {
  var host = parseURL(url).host;
  var registry = this._registries[host];

  if (registry === undefined) {
    registry = (this._registries[host] = new Registry(host));
  }

  return registry.verbs;
};

function Pretender(/* routeMap1, routeMap2, ...*/) {
  this.hosts = new Hosts();

  this.handlers = [];
  this.handledRequests = [];
  this.passthroughRequests = [];
  this.unhandledRequests = [];
  this.requestReferences = [];

  // reference the native XMLHttpRequest object so
  // it can be restored later
  this._nativeXMLHttpRequest = self.XMLHttpRequest;

  // capture xhr requests, channeling them into
  // the route map.
  self.XMLHttpRequest = interceptor(this);

  // 'start' the server
  this.running = true;

  // trigger the route map DSL.
  for (var i = 0; i < arguments.length; i++) {
    this.map(arguments[i]);
  }
}

function interceptor(pretender) {
  function FakeRequest() {
    // super()
    FakeXMLHttpRequest.call(this);
  }
  // extend
  var proto = new FakeXMLHttpRequest();
  proto.send = function send() {
    if (!pretender.running) {
      throw new Error('You shut down a Pretender instance while there was a pending request. ' +
            'That request just tried to complete. Check to see if you accidentally shut down ' +
            'a pretender earlier than you intended to');
    }

    FakeXMLHttpRequest.prototype.send.apply(this, arguments);
    if (!pretender.checkPassthrough(this)) {
      pretender.handleRequest(this);
    } else {
      var xhr = createPassthrough(this);
      xhr.send.apply(xhr, arguments);
    }
  };

  function createPassthrough(fakeXHR) {
    // event types to handle on the xhr
    var evts = ['error', 'timeout', 'abort', 'readystatechange'];

    // event types to handle on the xhr.upload
    var uploadEvents = ['progress'];

    // properties to copy from the native xhr to fake xhr
    var lifecycleProps = ['readyState', 'responseText', 'responseXML', 'status', 'statusText'];

    var xhr = fakeXHR._passthroughRequest = new pretender._nativeXMLHttpRequest();

    if (fakeXHR.responseType === 'arraybuffer') {
      lifecycleProps = ['readyState', 'response', 'status', 'statusText'];
      xhr.responseType = fakeXHR.responseType;
    }

    // Use onload if the browser supports it
    if ('onload' in xhr) {
      evts.push('load');
    }

    // add progress event for async calls
    if (fakeXHR.async && fakeXHR.responseType !== 'arraybuffer') {
      evts.push('progress');
    }

    // update `propertyNames` properties from `fromXHR` to `toXHR`
    function copyLifecycleProperties(propertyNames, fromXHR, toXHR) {
      for (var i = 0; i < propertyNames.length; i++) {
        var prop = propertyNames[i];
        if (fromXHR[prop]) {
          toXHR[prop] = fromXHR[prop];
        }
      }
    }

    // fire fake event on `eventable`
    function dispatchEvent(eventable, eventType, event) {
      eventable.dispatchEvent(event);
      if (eventable['on' + eventType]) {
        eventable['on' + eventType](event);
      }
    }

    // set the on- handler on the native xhr for the given eventType
    function createHandler(eventType) {
      xhr['on' + eventType] = function(event) {
        copyLifecycleProperties(lifecycleProps, xhr, fakeXHR);
        dispatchEvent(fakeXHR, eventType, event);
      };
    }

    // set the on- handler on the native xhr's `upload` property for
    // the given eventType
    function createUploadHandler(eventType) {
      if (xhr.upload) {
        xhr.upload['on' + eventType] = function(event) {
          dispatchEvent(fakeXHR.upload, eventType, event);
        };
      }
    }

    xhr.open(fakeXHR.method, fakeXHR.url, fakeXHR.async, fakeXHR.username, fakeXHR.password);

    var i;
    for (i = 0; i < evts.length; i++) {
      createHandler(evts[i]);
    }
    for (i = 0; i < uploadEvents.length; i++) {
      createUploadHandler(uploadEvents[i]);
    }

    if (fakeXHR.async) {
      xhr.timeout = fakeXHR.timeout;
      xhr.withCredentials = fakeXHR.withCredentials;
    }
    for (var h in fakeXHR.requestHeaders) {
      xhr.setRequestHeader(h, fakeXHR.requestHeaders[h]);
    }
    return xhr;
  }

  proto._passthroughCheck = function(method, args) {
    if (this._passthroughRequest) {
      return this._passthroughRequest[method].apply(this._passthroughRequest, args);
    }
    return FakeXMLHttpRequest.prototype[method].apply(this, args);
  };

  proto.abort = function abort() {
    return this._passthroughCheck('abort', arguments);
  };

  proto.getResponseHeader = function getResponseHeader() {
    return this._passthroughCheck('getResponseHeader', arguments);
  };

  proto.getAllResponseHeaders = function getAllResponseHeaders() {
    return this._passthroughCheck('getAllResponseHeaders', arguments);
  };

  FakeRequest.prototype = proto;
  return FakeRequest;
}

function verbify(verb) {
  return function(path, handler, async) {
    this.register(verb, path, handler, async);
  };
}

function scheduleProgressEvent(request, startTime, totalTime) {
  setTimeout(function() {
    if (!request.aborted && !request.status) {
      var ellapsedTime = new Date().getTime() - startTime.getTime();
      request.upload._progress(true, ellapsedTime, totalTime);
      request._progress(true, ellapsedTime, totalTime);
      scheduleProgressEvent(request, startTime, totalTime);
    }
  }, 50);
}

function isArray(array) {
  return Object.prototype.toString.call(array) === '[object Array]';
}

var PASSTHROUGH = {};

Pretender.prototype = {
  get: verbify('GET'),
  post: verbify('POST'),
  put: verbify('PUT'),
  'delete': verbify('DELETE'),
  patch: verbify('PATCH'),
  head: verbify('HEAD'),
  options: verbify('OPTIONS'),
  map: function(maps) {
    maps.call(this);
  },
  register: function register(verb, url, handler, async) {
    if (!handler) {
      throw new Error('The function you tried passing to Pretender to handle ' +
        verb + ' ' + url + ' is undefined or missing.');
    }

    handler.numberOfCalls = 0;
    handler.async = async;
    this.handlers.push(handler);

    var registry = this.hosts.forURL(url)[verb];

    registry.add([{
      path: parseURL(url).fullpath,
      handler: handler
    }]);
  },
  passthrough: PASSTHROUGH,
  checkPassthrough: function checkPassthrough(request) {
    var verb = request.method.toUpperCase();

    var path = parseURL(request.url).fullpath;

    verb = verb.toUpperCase();

    var recognized = this.hosts.forURL(request.url)[verb].recognize(path);
    var match = recognized && recognized[0];
    if (match && match.handler === PASSTHROUGH) {
      this.passthroughRequests.push(request);
      this.passthroughRequest(verb, path, request);
      return true;
    }

    return false;
  },
  handleRequest: function handleRequest(request) {
    var verb = request.method.toUpperCase();
    var path = request.url;

    var handler = this._handlerFor(verb, path, request);

    if (handler) {
      handler.handler.numberOfCalls++;
      var async = handler.handler.async;
      this.handledRequests.push(request);

      try {
        var statusHeadersAndBody = handler.handler(request);
        if (!isArray(statusHeadersAndBody)) {
          var note = 'Remember to `return [status, headers, body];` in your route handler.';
          throw new Error('Nothing returned by handler for ' + path + '. ' + note);
        }

        var status = statusHeadersAndBody[0],
            headers = this.prepareHeaders(statusHeadersAndBody[1]),
            body = this.prepareBody(statusHeadersAndBody[2], headers),
            pretender = this;

        this.handleResponse(request, async, function() {
          request.respond(status, headers, body);
          pretender.handledRequest(verb, path, request);
        });
      } catch (error) {
        this.erroredRequest(verb, path, request, error);
        this.resolve(request);
      }
    } else {
      this.unhandledRequests.push(request);
      this.unhandledRequest(verb, path, request);
    }
  },
  handleResponse: function handleResponse(request, strategy, callback) {
    var delay = typeof strategy === 'function' ? strategy() : strategy;
    delay = typeof delay === 'boolean' || typeof delay === 'number' ? delay : 0;

    if (delay === false) {
      callback();
    } else {
      var pretender = this;
      pretender.requestReferences.push({
        request: request,
        callback: callback
      });

      if (delay !== true) {
        scheduleProgressEvent(request, new Date(), delay);
        setTimeout(function() {
          pretender.resolve(request);
        }, delay);
      }
    }
  },
  resolve: function resolve(request) {
    for (var i = 0, len = this.requestReferences.length; i < len; i++) {
      var res = this.requestReferences[i];
      if (res.request === request) {
        res.callback();
        this.requestReferences.splice(i, 1);
        break;
      }
    }
  },
  requiresManualResolution: function(verb, path) {
    var handler = this._handlerFor(verb.toUpperCase(), path, {});
    if (!handler) { return false; }

    var async = handler.handler.async;
    return typeof async === 'function' ? async() === true : async === true;
  },
  prepareBody: function(body) { return body; },
  prepareHeaders: function(headers) { return headers; },
  handledRequest: function(/* verb, path, request */) { /* no-op */},
  passthroughRequest: function(/* verb, path, request */) { /* no-op */},
  unhandledRequest: function(verb, path/*, request */) {
    throw new Error('Pretender intercepted ' + verb + ' ' +
      path + ' but no handler was defined for this type of request');
  },
  erroredRequest: function(verb, path, request, error) {
    error.message = 'Pretender intercepted ' + verb + ' ' +
      path + ' but encountered an error: ' + error.message;
    throw error;
  },
  _handlerFor: function(verb, url, request) {
    var registry = this.hosts.forURL(url)[verb];
    var matches = registry.recognize(parseURL(url).fullpath);

    var match = matches ? matches[0] : null;
    if (match) {
      request.params = match.params;
      request.queryParams = matches.queryParams;
    }

    return match;
  },
  shutdown: function shutdown() {
    self.XMLHttpRequest = this._nativeXMLHttpRequest;

    // 'stop' the server
    this.running = false;
  }
};

Pretender.parseURL = parseURL;
Pretender.Hosts = Hosts;
Pretender.Registry = Registry;

if (typeof module === 'object') {
  module.exports = Pretender;
} else if (typeof define !== 'undefined') {
  define('pretender', [], function() {
    return Pretender;
  });
}
self.Pretender = Pretender;
}(self));
<|endoftext|>


<commit_before>var ngrokLauncher = require('screener-ngrok');
var Promise = require('bluebird');
var url = require('url');
var sauceConnectLauncher = require('sauce-connect-launcher');
var path = require('path');
var sauceConnection;

exports.connect = function({ ngrok, sauce }, tries = 0) {
  if (sauce) {
    return new Promise(function(resolve, reject) {
      sauceConnectLauncher({
        username: sauce.username,
        accessKey: sauce.accessKey,
        tunnelIdentifier: sauce.tunnelIdentifier,
        logfile: path.resolve(__dirname, '../sauce-connect.log'),
      }, function (err, sauceConnectProcess) {
        sauceConnection = sauceConnectProcess;
        if (err) {
          reject(err);
        }
        console.log('Sauce Connect ready');
        resolve();
      });
    })
      .catch(ex => {
        if (tries < 2) {
          // on error, wait and retry
          return Promise.delay(1000).then(() =>
            exports.connect({ sauce }, tries + 1)
          );
        }
        throw ex;
      });
  }

  if (ngrok) {
    var { host, token } = ngrok;
    if (!token) {
      return Promise.reject(new Error('No Tunnel Token'));
    }
    var href = /^https?:\/\//.test(host) ? host : 'http://' + host;
    var urlObj = url.parse(href);
    var options = {
      bind_tls: true,
      authtoken: token
    };
    // https:
    if (/^https/.test(urlObj.protocol)) {
      options.addr = 'https://' + urlObj.hostname;
      if (urlObj.port) {
        options.addr += ':' + urlObj.port;
      }
      options.host_header = 'rewrite';
    } else { // http:
      var port = urlObj.port || 80;
      options.addr = urlObj.hostname + ':' + port;
      options.host_header = urlObj.hostname;
      if (port !== 80) {
        // include port in host header when not default port 80
        options.host_header += ':' + port;
      }
    }
    var connect = Promise.promisify(ngrokLauncher.connect);
    return connect(options)
      .then(tunnelUrl => {
        var urlObj = url.parse(tunnelUrl);
        console.log('Connected private encrypted tunnel to ' + host + ' (' + urlObj.host.split('.')[0] + ')');
        return urlObj.host;
      })
      .catch(ex => {
        if (tries < 2) {
          // on error, wait and retry
          return Promise.delay(1000).then(() =>
            exports.connect({ ngrok }, tries + 1)
          );
        }
        throw ex;
      });
  }
};

exports.transformUrl = function(origUrl, host, tunnelHost) {
  var origUrlObj = url.parse(origUrl);
  var hostUrlObj = url.parse(/^https?:\/\//.test(host) ? host : 'http://' + host);
  var newUrl = origUrl;
  if (origUrlObj.host.toLowerCase() === hostUrlObj.host.toLowerCase() && origUrlObj.protocol === hostUrlObj.protocol) {
    origUrlObj.protocol = 'https';
    origUrlObj.host = tunnelHost;
    newUrl = url.format(origUrlObj);
  }
  return newUrl;
};

exports.disconnect = function() {
  return new Promise(function(resolve) {
    if (sauceConnection) {
      sauceConnection.close(function() {
        resolve();
      });
    } else {
      ngrokLauncher.disconnect();
      resolve();
    }
  });
};
<commit_msg>update path<commit_after>var ngrokLauncher = require('screener-ngrok');
var Promise = require('bluebird');
var url = require('url');
var sauceConnectLauncher = require('sauce-connect-launcher');
var path = require('path');
var sauceConnection;

exports.connect = function({ ngrok, sauce }, tries = 0) {
  if (sauce) {
    return new Promise(function(resolve, reject) {
      sauceConnectLauncher({
        username: sauce.username,
        accessKey: sauce.accessKey,
        tunnelIdentifier: sauce.tunnelIdentifier,
        logfile: path.resolve(__dirname, '../../../sauce-connect.log'),
      }, function (err, sauceConnectProcess) {
        sauceConnection = sauceConnectProcess;
        if (err) {
          reject(err);
        }
        console.log('Sauce Connect ready');
        resolve();
      });
    })
      .catch(ex => {
        if (tries < 2) {
          // on error, wait and retry
          return Promise.delay(1000).then(() =>
            exports.connect({ sauce }, tries + 1)
          );
        }
        throw ex;
      });
  }

  if (ngrok) {
    var { host, token } = ngrok;
    if (!token) {
      return Promise.reject(new Error('No Tunnel Token'));
    }
    var href = /^https?:\/\//.test(host) ? host : 'http://' + host;
    var urlObj = url.parse(href);
    var options = {
      bind_tls: true,
      authtoken: token
    };
    // https:
    if (/^https/.test(urlObj.protocol)) {
      options.addr = 'https://' + urlObj.hostname;
      if (urlObj.port) {
        options.addr += ':' + urlObj.port;
      }
      options.host_header = 'rewrite';
    } else { // http:
      var port = urlObj.port || 80;
      options.addr = urlObj.hostname + ':' + port;
      options.host_header = urlObj.hostname;
      if (port !== 80) {
        // include port in host header when not default port 80
        options.host_header += ':' + port;
      }
    }
    var connect = Promise.promisify(ngrokLauncher.connect);
    return connect(options)
      .then(tunnelUrl => {
        var urlObj = url.parse(tunnelUrl);
        console.log('Connected private encrypted tunnel to ' + host + ' (' + urlObj.host.split('.')[0] + ')');
        return urlObj.host;
      })
      .catch(ex => {
        if (tries < 2) {
          // on error, wait and retry
          return Promise.delay(1000).then(() =>
            exports.connect({ ngrok }, tries + 1)
          );
        }
        throw ex;
      });
  }
};

exports.transformUrl = function(origUrl, host, tunnelHost) {
  var origUrlObj = url.parse(origUrl);
  var hostUrlObj = url.parse(/^https?:\/\//.test(host) ? host : 'http://' + host);
  var newUrl = origUrl;
  if (origUrlObj.host.toLowerCase() === hostUrlObj.host.toLowerCase() && origUrlObj.protocol === hostUrlObj.protocol) {
    origUrlObj.protocol = 'https';
    origUrlObj.host = tunnelHost;
    newUrl = url.format(origUrlObj);
  }
  return newUrl;
};

exports.disconnect = function() {
  return new Promise(function(resolve) {
    if (sauceConnection) {
      sauceConnection.close(function() {
        resolve();
      });
    } else {
      ngrokLauncher.disconnect();
      resolve();
    }
  });
};
<|endoftext|>