<commit_before>// async cookies API polyfill for document contexts
if (self.document) (function() {
  'use strict';

  const DISALLOWED_IN_COOKIE_NAME_RE = /[()<>@,;:\\""\/\[\]?={} \0-\x1f\x7f]|[^\x21-\x7e\u00A0-\u{10FFFF}]/u;
  const DISALLOWED_IN_COOKIE_VALUE_RE = /[^\x2D-\x3A\x21\x23-\x2B\x3C-\x5B\x5D-\x7E\u00A0-\u{10FFFF}]/u;
  // based on https://tools.ietf.org/html/rfc3986#appendix-B
  const uriRegExp = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/;
  const uriSchemeGroup = 2;
  const uriAuthorityGroup = 4;
  const uriPathGroup = 5;
  const defUriGroups = String(location.href).match(uriRegExp) || [];
  const dirName = path => path.replace(/(^|\/)\.\.$/, '$1../').replace(/[^\/]+$/, '');
  const defReadPath = dirName(defUriGroups[uriPathGroup] || '/');
  const defOrigin = defUriGroups[uriSchemeGroup] + '://' + defUriGroups[uriAuthorityGroup];
  const defSecure = defUriGroups[uriSchemeGroup] === 'https';
  const resolvePath = path => {
    path = String(path || defReadPath);
    if (path[0] !== '/') path = defReadPath + path;
    let pathSegments = path.split('/');
    let resolvedPathSegments = [];
    pathSegments.forEach(segment => {
      if (segment === '..') resolvedPathSegments.pop();
      else resolvedPathSegments.push(segment);
    });
    return resolvedPathSegments.join('/');
  };
  const readPathForUrl = url => {
    if (url == null) url = defReadPath;
    url = String(url);
    const urlGroups = url.match(uriRegExp) || [];
    const urlPath = resolvePath(dirName(String(urlGroups[uriPathGroup] || defReadPath)));
    const origin = [
      urlGroups[uriSchemeGroup] || defUriGroups[uriSchemeGroup],
      urlGroups[uriAuthorityGroup] || defUriGroups[uriAuthorityGroup]].join('://').toLowerCase();
    if ((origin + urlPath) !== (defOrigin + defReadPath)) {
      // TODO: iframe with unpredictable long pathname inside
      // requested path, maybe by appending as many
      // randomly-selected '$' and ':' as needed to reach 2083 chars
      // in the URL could be used to read same-origin, different-path cookies.
      throw new SyntaxError([
        'Cannot read cookies at requested URL ',
        JSON.stringify(url),
        ' from ',
        JSON.stringify(defOrigin + defReadPath),
        ': not yet implemented.'].join(''));
    }
    return defReadPath;
  };
  class AsyncCookieJar_ {
    constructor(document) {
      this.doc_ = document;
    }
    async set(setCookieString) {
      console.info('Set-Cookie: %s', setCookieString);
      this.doc_.cookie = setCookieString;
    }
    async get() {
      return this.doc_.cookie;
    }
  }
  const SECURE_PREFIX = '__Secure-', HOST_PREFIX = '__Host-';
  class CookieStore {
    constructor(asyncCookieJar) {
      this.asyncCookieJar_ = asyncCookieJar;
    }
    async has(nameOrOptions, moreOptions) {
      let cookieList = await this.getAll(nameOrOptions, moreOptions);
      return cookieList.length >= 1;
    }
    async get(nameOrOptions, moreOptions) {
      let cookieList = await this.getAll(nameOrOptions, moreOptions);
      return cookieList[0];
    }
    async getAll(nameOrOptions, moreOptions) {
      let options = Object.assign(
        {},
        (typeof nameOrOptions === 'object') ? nameOrOptions : {name: nameOrOptions},
        moreOptions);
      let {name, url = defReadPath, matchType} = options || {};
      if (name != null) {
      	name = String(name);
        if (name.indexOf(';') !== -1) throw new SyntaxError('Character ";" is not allowed in cookie name');
        if (name.indexOf('=') !== -1) throw new SyntaxError('Character "=" is not allowed in cookie name');
      } else {
        name = '';
        if (matchType == null) matchType = 'startsWith';
      }
      if (matchType == null) matchType = 'equals';
      matchType = String(matchType);
      if (matchType !== 'equals' && matchType !== 'startsWith') {
        throw new SyntaxError('Unrecognized matchType ' + JSON.stringify(matchType));
      }
      url = readPathForUrl(url);
      let cookieList = [];
      let jar = String(await this.asyncCookieJar_.get() || '');
      for (let i = 0, j = jar.length, k = jar.indexOf(';');
           k = (k == -1) ? j : k, i < j;
           i = k + 1 + (jar[k + 1] == ' ' ? 1 : 0), k = jar.indexOf(';', i)) {
       	let nv = jar.substr(i, k - i), c = nv.indexOf('=');
       	let n = (c === -1) ? '' : nv.substr(0, c);
        // NOTE: some of the reported names and/or values will not be allowed in write operations.
        if (name === n || matchType === 'startsWith' && n.substr(0, name.length) === name) {
          let v = nv.substr(c + 1);
          cookieList.push({name: n, value: v});
        }
      }
      return cookieList;
    }
    async delete(name, options) {
      await this.set(name, undefined, options);
    }
    async set(name, value, options) {
      if (name == null) name = '';
      name = String(name);
      if (name.match(DISALLOWED_IN_COOKIE_NAME_RE) || name !== decodeURIComponent(encodeURIComponent(name))) {
        throw new SyntaxError('Unsupported character in cookie name');
      }
      options = options || {};
      let expires = null;
      let maxAge = null;
      let expiresAsNumber = (new Date(Number(options.expires))).getTime();
      // FIXME: this should instead use the HTTP Set-Cookie date/time parsing algorithm
      let parsedExpires = new Date(String(options.expires));
      if (options.expires != null && !isNaN(expiresAsNumber)) {
        expires = (new Date(expiresAsNumber)).toGMTString();
      } else if (options.expires != null) {
        if (isNaN(parsedExpires.getTime())) throw new SyntaxError('Unable to parse cookie "expires" attribute');
        expires = parsedExpires.toGMTString();
      }
      if (expires && expires.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "expires" attribute');
      }
      let domain = null;
      if (options.domain != null) domain = String(options.domain);
      if (domain != null && domain.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "domain" attribute');
      }
      if (domain != null) domain = new URL(location.protocol + '//' + domain).host;
      // FIXME: this should also check the public suffix list
      if (domain != null &&
          domain != location.host &&
          domain != location.hostname &&
          !location.hostname.endsWith('.' + domain)) {
        throw new SyntaxError('Cookie "domain" attribute does not match host');
      }
      let path = String(options.path || '/');
      if (path[0] !== '/') throw new SyntaxError('The "path" attribute must begin with "/"');
      if (path.match(/(^|\/)\.\.(\/|$)/)) {
        throw new SyntaxError('The cookie "path" attribute must not contain ".." path segments');
      }
      if (path.indexOf(';') !== -1) throw new SyntaxError('Character ";" is not allowed in cookie "path" attribute');
      if (path.indexOf('?') !== -1) throw new SyntaxError('Character "?" is not allowed in cookie "path" attribute');
      if (path.indexOf('#') !== -1) throw new SyntaxError('Character "#" is not allowed in cookie "path" attribute');
      // Add implicit trailing '/' if omitted. Does not match IE Set-Cookie behavior.
      path = path.replace(/([^\/]$)/, '$1/');
      let secure = options.secure;
      if (secure == null) secure = defSecure;
      secure = !!secure;
      if (secure && !defSecure) throw new SyntaxError('Secure cookies can only be modified from secure contexts');
      let httpOnly = !!options.httpOnly;
      if (value == null && maxAge == null && expires == null) maxAge = 0;
      value = String(value || '');
      if (value.match(DISALLOWED_IN_COOKIE_VALUE_RE) || value !== decodeURIComponent(encodeURIComponent(value))) {
        // Does not match document.cookie behavior!
        throw new SyntaxError('Unsupported character in cookie value');
      }
      if (name.substr(0, SECURE_PREFIX.length) === SECURE_PREFIX) {
        if (!defSecure) {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(SECURE_PREFIX),
            ' prefix can only be modified from secure contexts'].join(''));
        }
        if (!secure) {
          throw new SyntaxError('Cookies with the ' + JSON.stringify(SECURE_PREFIX) + ' prefix must use the Secure flag');
        }
      } else if (name.substr(0, HOST_PREFIX.length) === HOST_PREFIX) {
        if (!defSecure) {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(HOST_PREFIX),
            ' prefix can only be modified from secure contexts'].join(''));
        }
        if (!secure) {
          throw new SyntaxError('Cookies with the ' + JSON.stringify(HOST_PREFIX) + ' prefix must use the secure flag');
        }
        if (path !== '/') {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(HOST_PREFIX),
            ' prefix must have path ',
            JSON.stringify('/')].join(''));
        }
        if (domain != null) {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(HOST_PREFIX),
            ' prefix cannot have the domain parameter'].join(''));
        }
      }
      let setCookieParts = [name, '=', value];
      if (domain != null) setCookieParts.push('; domain=', domain);
      if (path != null) setCookieParts.push('; path=', path);
      if (expires != null) setCookieParts.push('; expires=', expires);
      if (maxAge != null) setCookieParts.push('; max-age=', maxAge);
      if (secure) setCookieParts.push('; secure');
      if (httpOnly) setCookieParts.push('; httpOnly');
      let setCookie = setCookieParts.join('');
      await this.asyncCookieJar_.set(setCookie);
    }
  };
  const FAST_OBSERVER_INTERVAL = 200, SLOW_OBSERVER_INTERVAL = 5000; // ms
  const VERY_LOW_BATTERY_THRESHOLD = 0.05, LOW_BATTERY_THRESHOLD = 0.15; // 1.0 = charged
  class CookieObserver {
    constructor(callback) {
      this.callback_ = callback;
      this.timer_ = null;
      this.interests_ = [];
    }
    observe(cookieStore, interests) {
      const copiedInterests = [];
      (interests || [{}]).forEach(({name, url = defReadPath, matchType} = {}) => {
        if (name != null) {
        	name = String(name);
          if (name.indexOf(';') !== -1) throw new SyntaxError('Character ";" is not allowed in cookie name');
          if (name.indexOf('=') !== -1) throw new SyntaxError('Character "=" is not allowed in cookie name');
        } else {
          name = '';
          if (matchType == null) matchType = 'startsWith';
        }
        url = String(url || defReadPath);
        let path = readPathForUrl(url);
        // readPathForUrl will throw or return defReadPath so we can ignore path past this point
        if (matchType == null) matchType = 'equals';
        matchType = String(matchType);
        if (matchType !== 'equals' && matchType !== 'startsWith') {
          throw new SyntaxError('Unrecognized matchType ' + JSON.stringify(matchType));
        }
        copiedInterests.push({name: name, url: url, matchType: matchType});
      });
      if (!copiedInterests.length) return;
      this.interests_.push({cookieStore: cookieStore, interests: copiedInterests, cookies_: null});
      this.schedule_(true);
    }
    disconnect() {
      this.interests_ = null;
      this.snapshots_ = null;
      if (this.timer_) {
        clearTimeout(this.timer_);
        this.timer_ = null;
      }
    }
    schedule_(skipWaiting) {
      navigator.getBattery().then(batteryManager => {
        if (this.timer_) return;
        let interval = SLOW_OBSERVER_INTERVAL;
        if (self.document.visibilityState === 'visible') {
          const level = batteryManager.level;
          if (level > VERY_LOW_BATTERY_THRESHOLD && level > LOW_BATTERY_THRESHOLD || batteryManager.charging) {
            interval = FAST_OBSERVER_INTERVAL;
          }
        }
        if (skipWaiting) interval = 0;
        this.timer_ = setTimeout(() => this.tick_().then(() => this.schedule_(false)), interval);
      });
    }
    async tick_() {
      this.timer_ = null;
      let reported = {};
      let observed = [];
      let allCookies = null;
      let forceReport = false;
      let oldCookieStore = null;
      let storeIndex = -1;
      this.interests_.forEach(interestEntry => {
        let {cookieStore, interests, cookies_} = interestEntry;
        if (oldCookieStore !== cookieStore || allCookies === null) {
          oldCookieStore = cookieStore;
          ++storeIndex;
          allCookies = await cookieStore.getAll();
        }
        let oldCookies = cookies_;
        let newCookies = interestEntry.cookies_ = allCookies;
        let oldCookiesFlat = {};
        (oldCookies || []).forEach(({name, value}) => {
          oldCookiesFlat[name + '='] = oldCookiesFlat[name + '='] || [];
          oldCookiesFlat[name + '='].push(name + '=' + value);
        });
        let newCookiesFlat = {};
        newCookies.forEach(({name, value}) => {
          newCookiesFlat[name + '='] = newCookiesFlat[name + '='] || [];
          newCookiesFlat[name + '='].push(name + '=' + value);
        });
        let changes = [];
        let newSame = {};
        (oldCookies || []).forEach(({name, value}, index) => {
          if (oldCookiesFlat[name + '='].join(';') === (newCookiesFlat[name + '='] || []).join(';')) {
            newSame[name + '='] = true;
          } else {
            changes.push({type: 'hidden', name: name, value: value, index: index});
          }
        });
        newCookies.forEach(({name, value}, index) => {
          if (!newSame[name + '=']) changes.push({type: 'visible', name: name, value: value, index: index});
        });
        if (oldCookies == null || changes.length > 0) {
          interests.forEach(({name, url, matchType}) => {
            let matching =
              changes.filter(change => name === change.name || matchType === 'startsWith' && change.name.startsWith(name));
            matching.forEach(({type, name, value, index}) => {
              let serialized = storeIndex + ';' + type + ';' + name + '=' + value + ';' + index + ';' + url;
              if (reported[serialized]) return;
              observed.push({type: type, name: name, value: value, url: url, cookieStore: cookieStore, index: index});
              reported[serialized] = true;
            });
          });
        }
        if (oldCookies == null) forceReport = true;
      });
      if (forceReport || observed.length) this.callback_.call(null, observed, this);
    }
  };
  if (!self.cookieStore) self.cookieStore = new CookieStore(new AsyncCookieJar_(self.document));
  if (!self.CookieObserver) self.CookieObserver = CookieObserver;
})();
<commit_msg>Fix typo and racing during observation<commit_after>// async cookies API polyfill for document contexts
if (self.document) (function() {
  'use strict';

  const DISALLOWED_IN_COOKIE_NAME_RE = /[()<>@,;:\\""\/\[\]?={} \0-\x1f\x7f]|[^\x21-\x7e\u00A0-\u{10FFFF}]/u;
  const DISALLOWED_IN_COOKIE_VALUE_RE = /[^\x2D-\x3A\x21\x23-\x2B\x3C-\x5B\x5D-\x7E\u00A0-\u{10FFFF}]/u;
  // based on https://tools.ietf.org/html/rfc3986#appendix-B
  const uriRegExp = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/;
  const uriSchemeGroup = 2;
  const uriAuthorityGroup = 4;
  const uriPathGroup = 5;
  const defUriGroups = String(location.href).match(uriRegExp) || [];
  const dirName = path => path.replace(/(^|\/)\.\.$/, '$1../').replace(/[^\/]+$/, '');
  const defReadPath = dirName(defUriGroups[uriPathGroup] || '/');
  const defOrigin = defUriGroups[uriSchemeGroup] + '://' + defUriGroups[uriAuthorityGroup];
  const defSecure = defUriGroups[uriSchemeGroup] === 'https';
  const resolvePath = path => {
    path = String(path || defReadPath);
    if (path[0] !== '/') path = defReadPath + path;
    let pathSegments = path.split('/');
    let resolvedPathSegments = [];
    pathSegments.forEach(segment => {
      if (segment === '..') resolvedPathSegments.pop();
      else resolvedPathSegments.push(segment);
    });
    return resolvedPathSegments.join('/');
  };
  const readPathForUrl = url => {
    if (url == null) url = defReadPath;
    url = String(url);
    const urlGroups = url.match(uriRegExp) || [];
    const urlPath = resolvePath(dirName(String(urlGroups[uriPathGroup] || defReadPath)));
    const origin = [
      urlGroups[uriSchemeGroup] || defUriGroups[uriSchemeGroup],
      urlGroups[uriAuthorityGroup] || defUriGroups[uriAuthorityGroup]].join('://').toLowerCase();
    if ((origin + urlPath) !== (defOrigin + defReadPath)) {
      // TODO: iframe with unpredictable long pathname inside
      // requested path, maybe by appending as many
      // randomly-selected '$' and ':' as needed to reach 2083 chars
      // in the URL could be used to read same-origin, different-path cookies.
      throw new SyntaxError([
        'Cannot read cookies at requested URL ',
        JSON.stringify(url),
        ' from ',
        JSON.stringify(defOrigin + defReadPath),
        ': not yet implemented.'].join(''));
    }
    return defReadPath;
  };
  class AsyncCookieJar_ {
    constructor(document) {
      this.doc_ = document;
    }
    async set(setCookieString) {
      console.info('Set-Cookie: %s', setCookieString);
      this.doc_.cookie = setCookieString;
    }
    async get() {
      return this.doc_.cookie;
    }
  }
  const SECURE_PREFIX = '__Secure-', HOST_PREFIX = '__Host-';
  class CookieStore {
    constructor(asyncCookieJar) {
      this.asyncCookieJar_ = asyncCookieJar;
    }
    async has(nameOrOptions, moreOptions) {
      let cookieList = await this.getAll(nameOrOptions, moreOptions);
      return cookieList.length >= 1;
    }
    async get(nameOrOptions, moreOptions) {
      let cookieList = await this.getAll(nameOrOptions, moreOptions);
      return cookieList[0];
    }
    async getAll(nameOrOptions, moreOptions) {
      let options = Object.assign(
        {},
        (typeof nameOrOptions === 'object') ? nameOrOptions : {name: nameOrOptions},
        moreOptions);
      let {name, url = defReadPath, matchType} = options || {};
      if (name != null) {
      	name = String(name);
        if (name.indexOf(';') !== -1) throw new SyntaxError('Character ";" is not allowed in cookie name');
        if (name.indexOf('=') !== -1) throw new SyntaxError('Character "=" is not allowed in cookie name');
      } else {
        name = '';
        if (matchType == null) matchType = 'startsWith';
      }
      if (matchType == null) matchType = 'equals';
      matchType = String(matchType);
      if (matchType !== 'equals' && matchType !== 'startsWith') {
        throw new SyntaxError('Unrecognized matchType ' + JSON.stringify(matchType));
      }
      url = readPathForUrl(url);
      let cookieList = [];
      let jar = String(await this.asyncCookieJar_.get() || '');
      for (let i = 0, j = jar.length, k = jar.indexOf(';');
           k = (k == -1) ? j : k, i < j;
           i = k + 1 + (jar[k + 1] == ' ' ? 1 : 0), k = jar.indexOf(';', i)) {
       	let nv = jar.substr(i, k - i), c = nv.indexOf('=');
       	let n = (c === -1) ? '' : nv.substr(0, c);
        // NOTE: some of the reported names and/or values will not be allowed in write operations.
        if (name === n || matchType === 'startsWith' && n.substr(0, name.length) === name) {
          let v = nv.substr(c + 1);
          cookieList.push({name: n, value: v});
        }
      }
      return cookieList;
    }
    async delete(name, options) {
      await this.set(name, undefined, options);
    }
    async set(name, value, options) {
      if (name == null) name = '';
      name = String(name);
      if (name.match(DISALLOWED_IN_COOKIE_NAME_RE) || name !== decodeURIComponent(encodeURIComponent(name))) {
        throw new SyntaxError('Unsupported character in cookie name');
      }
      options = options || {};
      let expires = null;
      let maxAge = null;
      let expiresAsNumber = (new Date(Number(options.expires))).getTime();
      // FIXME: this should instead use the HTTP Set-Cookie date/time parsing algorithm
      let parsedExpires = new Date(String(options.expires));
      if (options.expires != null && !isNaN(expiresAsNumber)) {
        expires = (new Date(expiresAsNumber)).toGMTString();
      } else if (options.expires != null) {
        if (isNaN(parsedExpires.getTime())) throw new SyntaxError('Unable to parse cookie "expires" attribute');
        expires = parsedExpires.toGMTString();
      }
      if (expires && expires.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "expires" attribute');
      }
      let domain = null;
      if (options.domain != null) domain = String(options.domain);
      if (domain != null && domain.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "domain" attribute');
      }
      if (domain != null) domain = new URL(location.protocol + '//' + domain).host;
      // FIXME: this should also check the public suffix list
      if (domain != null &&
          domain != location.host &&
          domain != location.hostname &&
          !location.hostname.endsWith('.' + domain)) {
        throw new SyntaxError('Cookie "domain" attribute does not match host');
      }
      let path = String(options.path || '/');
      if (path[0] !== '/') throw new SyntaxError('The "path" attribute must begin with "/"');
      if (path.match(/(^|\/)\.\.(\/|$)/)) {
        throw new SyntaxError('The cookie "path" attribute must not contain ".." path segments');
      }
      if (path.indexOf(';') !== -1) throw new SyntaxError('Character ";" is not allowed in cookie "path" attribute');
      if (path.indexOf('?') !== -1) throw new SyntaxError('Character "?" is not allowed in cookie "path" attribute');
      if (path.indexOf('#') !== -1) throw new SyntaxError('Character "#" is not allowed in cookie "path" attribute');
      // Add implicit trailing '/' if omitted. Does not match IE Set-Cookie behavior.
      path = path.replace(/([^\/]$)/, '$1/');
      let secure = options.secure;
      if (secure == null) secure = defSecure;
      secure = !!secure;
      if (secure && !defSecure) throw new SyntaxError('Secure cookies can only be modified from secure contexts');
      let httpOnly = !!options.httpOnly;
      if (value == null && maxAge == null && expires == null) maxAge = 0;
      value = String(value || '');
      if (value.match(DISALLOWED_IN_COOKIE_VALUE_RE) || value !== decodeURIComponent(encodeURIComponent(value))) {
        // Does not match document.cookie behavior!
        throw new SyntaxError('Unsupported character in cookie value');
      }
      if (name.substr(0, SECURE_PREFIX.length) === SECURE_PREFIX) {
        if (!defSecure) {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(SECURE_PREFIX),
            ' prefix can only be modified from secure contexts'].join(''));
        }
        if (!secure) {
          throw new SyntaxError('Cookies with the ' + JSON.stringify(SECURE_PREFIX) + ' prefix must use the Secure flag');
        }
      } else if (name.substr(0, HOST_PREFIX.length) === HOST_PREFIX) {
        if (!defSecure) {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(HOST_PREFIX),
            ' prefix can only be modified from secure contexts'].join(''));
        }
        if (!secure) {
          throw new SyntaxError('Cookies with the ' + JSON.stringify(HOST_PREFIX) + ' prefix must use the secure flag');
        }
        if (path !== '/') {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(HOST_PREFIX),
            ' prefix must have path ',
            JSON.stringify('/')].join(''));
        }
        if (domain != null) {
          throw new SyntaxError([
            'Cookies with the ',
            JSON.stringify(HOST_PREFIX),
            ' prefix cannot have the domain parameter'].join(''));
        }
      }
      let setCookieParts = [name, '=', value];
      if (domain != null) setCookieParts.push('; domain=', domain);
      if (path != null) setCookieParts.push('; path=', path);
      if (expires != null) setCookieParts.push('; expires=', expires);
      if (maxAge != null) setCookieParts.push('; max-age=', maxAge);
      if (secure) setCookieParts.push('; secure');
      if (httpOnly) setCookieParts.push('; httpOnly');
      let setCookie = setCookieParts.join('');
      await this.asyncCookieJar_.set(setCookie);
    }
  };
  const FAST_OBSERVER_INTERVAL = 200, SLOW_OBSERVER_INTERVAL = 5000; // ms
  const VERY_LOW_BATTERY_THRESHOLD = 0.05, LOW_BATTERY_THRESHOLD = 0.15; // 1.0 = charged
  class CookieObserver {
    constructor(callback) {
      this.callback_ = callback;
      this.timer_ = null;
      this.interests_ = [];
      this.newInterests_ = [];
      this.insideTick_ = false;
      this.newSchedule_ = false;
    }
    observe(cookieStore, interests) {
      if (cookieStore == null) throw new TypeError('Non-null first argument required');
      if (typeof cookieStore.getAll !== 'function') {
        throw new TypeError('Observed CookieStore must have a getAll method');
      }
      const copiedInterests = [];
      (interests || [{}]).forEach(({name, url = defReadPath, matchType} = {}) => {
        if (name != null) {
        	name = String(name);
          if (name.indexOf(';') !== -1) throw new SyntaxError('Character ";" is not allowed in cookie name');
          if (name.indexOf('=') !== -1) throw new SyntaxError('Character "=" is not allowed in cookie name');
        } else {
          name = '';
          if (matchType == null) matchType = 'startsWith';
        }
        url = String(url || defReadPath);
        let path = readPathForUrl(url);
        // readPathForUrl will throw or return defReadPath so we can ignore path past this point
        if (matchType == null) matchType = 'equals';
        matchType = String(matchType);
        if (matchType !== 'equals' && matchType !== 'startsWith') {
          throw new SyntaxError('Unrecognized matchType ' + JSON.stringify(matchType));
        }
        copiedInterests.push({name: name, url: url, matchType: matchType});
      });
      if (!copiedInterests.length) return;
      this.newInterests_.push({cookieStore: cookieStore, interests: copiedInterests, cookies_: null});
      this.schedule_(true);
    }
    disconnect() {
      this.interests_ = [];
      this.newInterests_ = [];
      if (this.timer_) {
        clearTimeout(this.timer_);
        this.timer_ = null;
      }
    }
    schedule_(skipWaiting) {
      if (this.insideTick_) {
        this.newSchedule_ = skipWaiting;
        return;
      }
      this.newSchedule_ = false;
      navigator.getBattery().then(batteryManager => {
        if (this.timer_ && !skipWaiting) return;
        if (this.timer_) {
          clearTimeout(this.timer_);
          this.timer_ = null;
        }
        let interval = SLOW_OBSERVER_INTERVAL;
        if (self.document.visibilityState === 'visible') {
          const level = batteryManager.level;
          if (level > VERY_LOW_BATTERY_THRESHOLD && level > LOW_BATTERY_THRESHOLD || batteryManager.charging) {
            interval = FAST_OBSERVER_INTERVAL;
          }
        }
        if (skipWaiting) interval = 0;
        let handler = () => this.tick_().then(() => this.schedule_(this.newSchedule_));
        this.timer_ = setTimeout(handler, interval);
      });
    }
    async tick_() {
      this.timer_ = null;
      this.newSchedule_ = false;
      this.insideTick_ = true;
      try {
        if (this.newInterests_.length) {
          this.interests_ = this.interests_.concat(this.interests_, this.newInterests_)
          this.newInterests_ = [];
        }
        let reported = {};
        let observed = [];
        let allCookies = null;
        let forceReport = false;
        let oldCookieStore = null;
        let storeIndex = -1;
        let interestEntries = this.interests_;
        for (let i = 0, j = interestEntries.length; i < j; ++i) {
          let {cookieStore, interests, cookies_} = interestEntries[i];
          if (oldCookieStore !== cookieStore) {
            oldCookieStore = cookieStore;
            ++storeIndex;
            allCookies = await cookieStore.getAll();
            // A call to disconnect() while awaiting a cookie store
            // snapshot causes an early return without reporting
            if (this.interests_ !== interestEntries) return;
          }
          let oldCookies = cookies_;
          let newCookies = interestEntries[i].cookies_ = allCookies;
          let oldCookiesFlat = {};
          (oldCookies || []).forEach(({name, value}) => {
            oldCookiesFlat[name + '='] = oldCookiesFlat[name + '='] || [];
            oldCookiesFlat[name + '='].push(name + '=' + value);
          });
          let newCookiesFlat = {};
          newCookies.forEach(({name, value}) => {
            newCookiesFlat[name + '='] = newCookiesFlat[name + '='] || [];
            newCookiesFlat[name + '='].push(name + '=' + value);
          });
          let changes = [];
          let newSame = {};
          (oldCookies || []).forEach(({name, value}, index) => {
            if (oldCookiesFlat[name + '='].join(';') === (newCookiesFlat[name + '='] || []).join(';')) {
              newSame[name + '='] = true;
            } else {
              changes.push({type: 'hidden', name: name, value: value, index: index});
            }
          });
          newCookies.forEach(({name, value}, index) => {
            if (!newSame[name + '=']) changes.push({type: 'visible', name: name, value: value, index: index});
          });
          if (oldCookies == null || changes.length > 0) {
            interests.forEach(({name, url, matchType}) => {
              let matching =
                changes.filter(change => name === change.name || matchType === 'startsWith' && change.name.startsWith(name));
              matching.forEach(({type, name, value, index}) => {
                let serialized = storeIndex + ';' + type + ';' + name + '=' + value + ';' + index + ';' + url;
                if (reported[serialized]) return;
                observed.push({type: type, name: name, value: value, url: url, cookieStore: cookieStore, index: index});
                reported[serialized] = true;
              });
            });
          }
          if (oldCookies == null) forceReport = true;
        }
        if (forceReport || observed.length) this.callback_.call(null, observed, this);
      } finally {
        this.insideTick_ = false;
      }
    }
  };
  if (!self.cookieStore) self.cookieStore = new CookieStore(new AsyncCookieJar_(self.document));
  if (!self.CookieObserver) self.CookieObserver = CookieObserver;
})();
<|endoftext|>


<commit_before>'use strict';

const config = require(`${__dirname}/../config/init`);

/**
 * makeAllRowRequestBody(str)
 * @param string field Field to query parameters from.
 * @param string from Date to query results from.
 * @param string to Date to query results to.
 * @return object Request to be performed body.
 */
exports.makeAllRowRequest = function make(field, size, from, to, response) {
  config.client.search({
    index: config.index,
    type: config.type,
    body: {
      size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                "@timestamp": { from, to },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          terms: {
            size,
            field,
          },
        },
      },
    },
  }).then((body) => {
    response.json({ success: true, hits: body.aggregations.results.buckets });
  }, (error) => {
    response.json({ success: false, message: error.message });
  });
};

exports.makeGameRequest = function (type, req, response) {
    config.client.count({
      index: config.index,
      type: config.type,
      body: {
          filter: {
            term: {
              target: 'https://seestats.org/game/' + type
            }
          }
      }
    }).then((body) => {
      response.json({ success: true, count: body.count });
  }, (error) => {
      response.json({ success: false, message: error.message });
  });
};

exports.makeTodayCountRequests = function (req, response) {
    config.client.count({
      index: config.index,
      type: config.type,
      body: {
          filter: {
            term: {
              target: 'https://seestats.org/index.html'
            }
          }
      }
    }).then((body) => {
        response.json({ success: true, count: body.count });
  }, (error) => {
    response.json({ success: false, message: error.message });
  });
};

exports.makeTopTargetRequests = function (req, response) {
    config.client.search({
      index: config.index,
      type: config.type,
      body: {
       size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                '@timestamp': {
                  from: '2016-03-01',
                  to: '2016-03-07' },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          terms: {
            size: 20,
            field: "target"
          },
        },
      },
      }
    }).then((body) => {
        response.json({ success: true, count: body.aggregations.results.buckets });
  }, (error) => {
    response.json({ success: false, message: error.message });
  });
};

exports.makeUniqueTargetsRequests = function (req, res) {
    config.client.search({
      index: config.index,
      type: config.type,
      body: {
       size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                '@timestamp': {
                  from: '2016-03-01',
                  to: '2016-03-07' },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          cardinality: {
            field: "target"
          },
        },
      },
      }
    }).then((body) => {
        res.json({ success: true, count: body.aggregations.results.value });
  }, (error) => {
    res.json({ success: false, message: error.message });
  });
};<commit_msg>Add helper function for top traffic<commit_after>'use strict';

const config = require(`${__dirname}/../config/init`);

/**
 * makeAllRowRequestBody(str)
 * @param string field Field to query parameters from.
 * @param string from Date to query results from.
 * @param string to Date to query results to.
 * @return object Request to be performed body.
 */
exports.makeAllRowRequest = function make(field, size, from, to, response) {
  config.client.search({
    index: config.index,
    type: config.type,
    body: {
      size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                "@timestamp": { from, to },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          terms: {
            size,
            field,
          },
        },
      },
    },
  }).then((body) => {
    response.json({ success: true, hits: body.aggregations.results.buckets });
  }, (error) => {
    response.json({ success: false, message: error.message });
  });
};

exports.makeGameRequest = function (type, req, response) {
    config.client.count({
      index: config.index,
      type: config.type,
      body: {
          filter: {
            term: {
              target: 'https://seestats.org/game/' + type
            }
          }
      }
    }).then((body) => {
      response.json({ success: true, count: body.count });
  }, (error) => {
      response.json({ success: false, message: error.message });
  });
};

exports.makeTodayCountRequests = function (req, response) {
    config.client.count({
      index: config.index,
      type: config.type,
      body: {
          filter: {
            term: {
              target: 'https://seestats.org/index.html'
            }
          }
      }
    }).then((body) => {
        response.json({ success: true, count: body.count });
  }, (error) => {
    response.json({ success: false, message: error.message });
  });
};

exports.makeTopTargetRequests = function (req, response) {
    config.client.search({
      index: config.index,
      type: config.type,
      body: {
       size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                '@timestamp': {
                  from: '2016-03-01',
                  to: '2016-03-07' },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          terms: {
            size: 20,
            field: "target"
          },
        },
      },
      }
    }).then((body) => {
        response.json({ success: true, count: body.aggregations.results.buckets });
  }, (error) => {
    response.json({ success: false, message: error.message });
  });
};

exports.makeUniqueTargetsRequests = function (req, res) {
    config.client.search({
      index: config.index,
      type: config.type,
      body: {
       size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                '@timestamp': {
                  from: '2016-03-01',
                  to: '2016-03-07' },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          cardinality: {
            field: "target"
          },
        },
      },
      }
    }).then((body) => {
        res.json({ success: true, count: body.aggregations.results.value });
  }, (error) => {
    res.json({ success: false, message: error.message });
  });
};

exports.makeCurrentlyActiveConnectionRequests = function (req, res) {
    const from = Date.now() - (60*60*1000);
    const to = Date.now();

    config.client.search({
      index: config.index,
      type: config.type,
      body: {
       size: 0,
      query: {
        bool: {
          must: [
            {
              range: {
                '@timestamp': {
                  from,
                  to },
              },
            },
          ],
        },
      },
      aggs: {
        results: {
          sum: {
            field: "traffic"
          },
        },
      },
      }
    }).then((body) => {
        res.json({ success: true, count: body.aggregations.results.value });
  }, (error) => {
    res.json({ success: false, message: error.message });
  });
};<|endoftext|>


<commit_before>var avatarResponder = require('./avatar_responder');
require('./message_templates');
var Chicisimo = {
  responders: []
};

Campfire.USER_ACTIONS = ['enter','leave','kick','conference_created','lock','unlock','topic_change','allow_guests','disallow_guests'];

swizzle(Campfire.Message, {
  authorElement: function($super) {
    if (Campfire.USER_ACTIONS.include(this.kind))
      return $super().select('span.author')[0];

    return $super();
  }
});

Campfire.Chicisimo = Class.create({
  initialize: function(chat) {
    chat.transcript.messages.forEach(this.executeResponders);

    chat.layoutmanager.layout();

    chat.windowmanager.scrollToBottom();
  },
  onMessagesInserted: function(messages) {
    var scrolledToBottom = chat.windowmanager.isScrolledToBottom();

    messages.forEach(this.executeResponders);

    if (scrolledToBottom) chat.windowmanager.scrollToBottom();
  },
  executeResponders: function(message) {
    Chicisimo.responders.forEach(function(responder) { responder(message); });
  }
});

Chicisimo.responders.push(avatarResponder);
Campfire.Responders.push("Chicisimo");
window.chat.installPropaneResponder("Chicisimo", "chicisimo");

<commit_msg>Capitalize Chicisimo.responders for consistency's sake<commit_after>var avatarResponder = require('./avatar_responder');
require('./message_templates');
var Chicisimo = {
  Responders: []
};

Campfire.USER_ACTIONS = ['enter','leave','kick','conference_created','lock','unlock','topic_change','allow_guests','disallow_guests'];

swizzle(Campfire.Message, {
  authorElement: function($super) {
    if (Campfire.USER_ACTIONS.include(this.kind))
      return $super().select('span.author')[0];

    return $super();
  }
});

Campfire.Chicisimo = Class.create({
  initialize: function(chat) {
    chat.transcript.messages.forEach(this.executeResponders);

    chat.layoutmanager.layout();

    chat.windowmanager.scrollToBottom();
  },
  onMessagesInserted: function(messages) {
    var scrolledToBottom = chat.windowmanager.isScrolledToBottom();

    messages.forEach(this.executeResponders);

    if (scrolledToBottom) chat.windowmanager.scrollToBottom();
  },
  executeResponders: function(message) {
    Chicisimo.Responders.forEach(function(responder) { responder(message); });
  }
});

Chicisimo.Responders.push(avatarResponder);
Campfire.Responders.push("Chicisimo");
window.chat.installPropaneResponder("Chicisimo", "chicisimo");

<|endoftext|>


<commit_before>'use strict';

exports.services = require('./services');
exports.controllers = require('./controllers');
exports.routes = require('./routes');<commit_msg>[src] fix models issue<commit_after>'use strict';

exports.services = require('./services');
exports.controllers = require('./controllers');
exports.routes = require('./routes');
exports.models = require('./models');<|endoftext|>


<commit_before>// Data access/propagation rules for apps

'use strict';

var aFrom            = require('es5-ext/array/from')
  , compose          = require('es5-ext/function/#/compose')
  , findKey          = require('es5-ext/object/find-key')
  , forEach          = require('es5-ext/object/for-each')
  , ensureCallable   = require('es5-ext/object/valid-callable')
  , ensureObject     = require('es5-ext/object/valid-object')
  , endsWith         = require('es5-ext/string/#/ends-with')
  , uncapitalize     = require('es5-ext/string/#/uncapitalize')
  , memoize          = require('memoizee')
  , Set              = require('es6-set')
  , deferred         = require('deferred')
  , unserializeValue = require('dbjs/_setup/unserialize/value')
  , ObservableSet    = require('observable-set')
  , ObservableMulti  = require('observable-multi-set')
  , Fragment         = require('data-fragment')
  , FragmentGroup    = require('data-fragment/group')
  , ensureDriver     = require('dbjs-persistence/ensure-driver')
  , unserializeView  = require('../utils/db-view/unserialize-ids')
  , getReducedFrag   = require('./data-fragments/get-reduced-object-fragments')
  , getRedRecFrag    = require('./data-fragments/get-reduced-records-fragments')
  , getObjFragment   = require('./data-fragments/get-direct-object-fragments')
  , getColFragments  = require('./data-fragments/get-collection-fragments')
  , getPartFragments = require('./data-fragments/get-part-object-fragments')
  , getDbRecordSet   = require('./utils/get-db-record-set')
  , bpListProps      = require('../apps-common/business-process-list-properties')
  , bpListCompProps  = require('../apps-common/business-process-list-computed-properties')
  , userListProps    = require('../apps/users-admin/user-list-properties')

  , create = Object.create, keys = Object.keys, stringify = JSON.stringify
  , emptyFragment = new Fragment()
  , isOfficialRoleName = RegExp.prototype.test.bind(/^official[A-Z]/);

var joinSets = function (sets) {
	var set = new ObservableMulti(sets);
	set.promise = deferred.map(sets, function (set) { return set.promise; });
	return set;
};
module.exports = function (dbDriver, data) {
	var userStorage = ensureDriver(dbDriver).getStorage('user')
	  , reducedStorage = dbDriver.getReducedStorage()
	  , getUserData = getObjFragment(userStorage)
	  , getBusinessProcessData = getObjFragment()
	  , getUserReducedData = getReducedFrag(userStorage)
	  , getReducedData = getReducedFrag(reducedStorage)
	  , resolveOfficialSteps, processingStepsMeta, processingStepsDefaultMap = create(null);

	ensureObject(data);
	processingStepsMeta = ensureObject(data.processingStepsMeta);

	// Configure official steps (per user) resolver
	var defaultOfficialStepsResolver = function (userId) {
		var rolesSet = getDbRecordSet(userStorage, userId + '/roles');
		var resultSet = rolesSet.map(unserializeValue).filter(isOfficialRoleName)
			.map(function (roleName) { return uncapitalize.call(roleName.slice('official'.length)); });
		resultSet.promise = rolesSet.promise;
		return resultSet;
	};
	if (data.normalizeOfficialStepsPaths != null) {
		resolveOfficialSteps = compose.bind(ensureCallable(data.resolveOfficialSteps),
			defaultOfficialStepsResolver);
	} else {
		resolveOfficialSteps = defaultOfficialStepsResolver;
	}

	// Resolve default step statuses (in most cases 'pending')
	forEach(data.processingStepsMeta, function (meta, stepShortPath) {
		var defaultKey = findKey(meta, function (conf) {
			return conf.default;
		});
		if (!defaultKey) throw new Error("Not found default key for " + stringify(stepShortPath));
		processingStepsDefaultMap[stepShortPath] = defaultKey;
	});
	var resolveDefaultStatus = function (stepShortPath) {
		var defaultKey = processingStepsDefaultMap[stepShortPath];
		if (!defaultKey) throw new Error("Unrecognized step short path " + stringify(stepShortPath));
		return defaultKey;
	};

	// Configure fragment resolvers
	// Non role specific
	var getUserFragment = function (id) {
		return getUserData(id,
			{ filter: function (data) { return !endsWith.call(data.id, '/password'); } });
	};
	var getFirstPageItems = memoize(function (storage, viewName) {
		var objects = new ObservableSet(), id = 'views/' + viewName + '/21';
		objects.promise = storage.getReduced(id)(function (data) {
			if (data) objects.reload(unserializeView(unserializeValue(data.value)));
		});
		storage.on('keyid:' + id, function (event) {
			objects.reload(unserializeView(unserializeValue(event.data.value)));
		});
		return objects;
	}, { primitive: true });
	var addOfficialStepsPendingSizes = (function () {
		var resolveFragment = function (stepsShortPaths) {
			var ids;
			if (stepsShortPaths.size <= 1) return emptyFragment;
			ids = [];
			stepsShortPaths.forEach(function (stepShortPath) {
				var defaultKey = resolveDefaultStatus(stepShortPath);
				ids.push('pendingBusinessProcesses/' + stepShortPath + '/' + defaultKey + '/totalSize');
			});
			return getRedRecFrag(reducedStorage, ids);
		};
		return function (userId, fragment) {
			var stepShortNames = resolveOfficialSteps(userId)
			  , current;
			current = resolveFragment(stepShortNames);
			fragment.addFragment(current);
			stepShortNames.on('change', function () {
				var nu = resolveFragment(stepShortNames);
				if (nu === current) return;
				fragment.addFragment(nu);
				fragment.deleteFragment(current);
				current = nu;
			});
		};
	}());

	// Users Admin resolvers
	var getUserListFragment = getPartFragments(userStorage, userListProps);
	var getRecentlyVisitedUsersFragment = function (userId) {
		var list = getDbRecordSet(userStorage, userId + '/recentlyVisited/users')
			.map(function (value) { return value.slice(1); });

		return getColFragments(list, getUserListFragment);
	};
	var getUsersAdminFragment = memoize(function () {
		var fragment = new FragmentGroup();
		// First page snapshot
		fragment.addFragment(getUserReducedData('views/usersAdmin'));
		// First page list data
		fragment.addFragment(getColFragments(getFirstPageItems(userStorage, 'usersAdmin'),
			getUserListFragment));
		return fragment;
	});

	// Official roles resolvers
	var getRecentlyVisitedBusinessProcessesFragment = function (userId, roleName) {
		var list = getDbRecordSet(userStorage,
			userId + '/recentlyVisited/businessProcesses/' + roleName)
			.map(function (value) { return value.slice(1); });

		return getColFragments(list, getBusinessProcessData);
	};
	var getBusinessProcessListFragment = getPartFragments(null,
		new Set(aFrom(bpListProps).concat(aFrom(bpListCompProps))));

	var getOfficialFragment = memoize(function (stepShortPath) {
		var fragment = new FragmentGroup()
		  , defaultStatusName = resolveDefaultStatus(stepShortPath);

		// To be visited (recently pending) business processes (full data)
		fragment.add(getColFragments(getFirstPageItems(reducedStorage, 'pendingBusinessProcesses/' +
			stepShortPath + '/' + defaultStatusName).toArray().slice(0, 10), getBusinessProcessData));
		// First page snapshot for each status
		fragment.addFragment(getReducedData('views/pendingBusinessProcesses/' + stepShortPath));
		// First page list data for each status
		fragment.addFragment(getColFragments(joinSets(
			keys(processingStepsMeta[stepShortPath]).map(function (status) {
				return getFirstPageItems(reducedStorage,
					'pendingBusinessProcesses/' + stepShortPath + '/' + status);
			})
		), getBusinessProcessListFragment));
		return fragment;
	}, { primitive: true });

	return memoize(function (appId) {
		var userId, roleName, shortRoleName, custom, fragment, initialBusinessProcesses, promise;

		appId = appId.split('.');
		userId = appId[0];
		roleName = appId[1];
		custom = appId[2];

		fragment = new FragmentGroup();

		// In all cases: User profile data
		fragment.addFragment(getUserFragment(userId));

		if (!roleName) return fragment; // Temporary inconsistent state (client migration)

		// Sizes of pending files in official roles of user
		addOfficialStepsPendingSizes(userId, fragment);

		if (roleName === 'user') {
			// User role:
			// - Business process data
			if (custom) {
				fragment.addFragment(getBusinessProcessData(custom));
			} else {
				initialBusinessProcesses = getDbRecordSet(userStorage,
					userId + '/initialBusinessProcesses');
				promise = initialBusinessProcesses.promise;
				initialBusinessProcesses = initialBusinessProcesses
					.map(function (value) { return value.slice(1); });
				initialBusinessProcesses.promise = promise;
				// - All initial business processes
				fragment.addFragment(getColFragments(initialBusinessProcesses, getBusinessProcessData));
			}
			return fragment;
		}
		if (roleName === 'usersAdmin') {
			// Recently visited users (full data)
			fragment.addFragment(getRecentlyVisitedUsersFragment(userId));
			// Users Admin specific data
			fragment.addFragment(getUsersAdminFragment());
			return fragment;
		}
		if (roleName === 'metaAdmin') return fragment;

		if (isOfficialRoleName(roleName)) {
			shortRoleName = uncapitalize.call(roleName.slice('official'.length));
			// Recently visited business processes (full data)
			fragment.addFragment(getRecentlyVisitedBusinessProcessesFragment(userId, shortRoleName));
			// Official role specific data
			fragment.addFragment(getOfficialFragment(shortRoleName));
			return fragment;
		}
		console.error("\n\nError: Unrecognized role " + roleName + "\n\n");
		return fragment;
	}, { primitive: true });
};
<commit_msg>Cleanup<commit_after>// Data access/propagation rules for apps

'use strict';

var aFrom            = require('es5-ext/array/from')
  , compose          = require('es5-ext/function/#/compose')
  , findKey          = require('es5-ext/object/find-key')
  , forEach          = require('es5-ext/object/for-each')
  , ensureCallable   = require('es5-ext/object/valid-callable')
  , ensureObject     = require('es5-ext/object/valid-object')
  , endsWith         = require('es5-ext/string/#/ends-with')
  , uncapitalize     = require('es5-ext/string/#/uncapitalize')
  , memoize          = require('memoizee')
  , Set              = require('es6-set')
  , deferred         = require('deferred')
  , unserializeValue = require('dbjs/_setup/unserialize/value')
  , ObservableSet    = require('observable-set')
  , ObservableMulti  = require('observable-multi-set')
  , Fragment         = require('data-fragment')
  , FragmentGroup    = require('data-fragment/group')
  , ensureDriver     = require('dbjs-persistence/ensure-driver')
  , unserializeView  = require('../utils/db-view/unserialize-ids')
  , getReducedFrag   = require('./data-fragments/get-reduced-object-fragments')
  , getRedRecFrag    = require('./data-fragments/get-reduced-records-fragments')
  , getObjFragment   = require('./data-fragments/get-direct-object-fragments')
  , getColFragments  = require('./data-fragments/get-collection-fragments')
  , getPartFragments = require('./data-fragments/get-part-object-fragments')
  , getDbRecordSet   = require('./utils/get-db-record-set')
  , bpListProps      = require('../apps-common/business-process-list-properties')
  , bpListCompProps  = require('../apps-common/business-process-list-computed-properties')
  , userListProps    = require('../apps/users-admin/user-list-properties')

  , create = Object.create, keys = Object.keys, stringify = JSON.stringify
  , emptyFragment = new Fragment()
  , isOfficialRoleName = RegExp.prototype.test.bind(/^official[A-Z]/);

var joinSets = function (sets) {
	var set = new ObservableMulti(sets);
	set.promise = deferred.map(sets, function (set) { return set.promise; });
	return set;
};
module.exports = function (dbDriver, data) {
	var userStorage = ensureDriver(dbDriver).getStorage('user')
	  , reducedStorage = dbDriver.getReducedStorage()
	  , getUserData = getObjFragment(userStorage)
	  , getBusinessProcessData = getObjFragment()
	  , getUserReducedData = getReducedFrag(userStorage)
	  , getReducedData = getReducedFrag(reducedStorage)
	  , resolveOfficialSteps, processingStepsMeta, processingStepsDefaultMap = create(null);

	ensureObject(data);
	processingStepsMeta = ensureObject(data.processingStepsMeta);

	// Configure official steps (per user) resolver
	var defaultOfficialStepsResolver = function (userId) {
		var rolesSet = getDbRecordSet(userStorage, userId + '/roles');
		var resultSet = rolesSet.map(unserializeValue).filter(isOfficialRoleName)
			.map(function (roleName) { return uncapitalize.call(roleName.slice('official'.length)); });
		resultSet.promise = rolesSet.promise;
		return resultSet;
	};
	if (data.normalizeOfficialStepsPaths != null) {
		resolveOfficialSteps = compose.bind(ensureCallable(data.resolveOfficialSteps),
			defaultOfficialStepsResolver);
	} else {
		resolveOfficialSteps = defaultOfficialStepsResolver;
	}

	// Resolve default step statuses (in most cases 'pending')
	forEach(data.processingStepsMeta, function (meta, stepShortPath) {
		var defaultKey = findKey(meta, function (conf) {
			return conf.default;
		});
		if (!defaultKey) throw new Error("Not found default key for " + stringify(stepShortPath));
		processingStepsDefaultMap[stepShortPath] = defaultKey;
	});
	var resolveDefaultStatus = function (stepShortPath) {
		var defaultKey = processingStepsDefaultMap[stepShortPath];
		if (!defaultKey) throw new Error("Unrecognized step short path " + stringify(stepShortPath));
		return defaultKey;
	};

	// Configure fragment resolvers
	// Non role specific
	var getUserFragment = function (id) {
		return getUserData(id,
			{ filter: function (data) { return !endsWith.call(data.id, '/password'); } });
	};
	var getFirstPageItems = memoize(function (storage, viewName) {
		var objects = new ObservableSet(), id = 'views/' + viewName + '/21';
		objects.promise = storage.getReduced(id)(function (data) {
			if (data) objects.reload(unserializeView(unserializeValue(data.value)));
		});
		storage.on('keyid:' + id, function (event) {
			objects.reload(unserializeView(unserializeValue(event.data.value)));
		});
		return objects;
	}, { primitive: true });
	var addOfficialStepsPendingSizes = (function () {
		var resolveFragment = function (stepsShortPaths) {
			var ids;
			if (stepsShortPaths.size <= 1) return emptyFragment;
			ids = [];
			stepsShortPaths.forEach(function (stepShortPath) {
				var defaultKey = resolveDefaultStatus(stepShortPath);
				ids.push('pendingBusinessProcesses/' + stepShortPath + '/' + defaultKey + '/totalSize');
			});
			return getRedRecFrag(reducedStorage, ids);
		};
		return function (userId, fragment) {
			var stepShortNames = resolveOfficialSteps(userId)
			  , current;
			current = resolveFragment(stepShortNames);
			fragment.addFragment(current);
			stepShortNames.on('change', function () {
				var nu = resolveFragment(stepShortNames);
				if (nu === current) return;
				fragment.addFragment(nu);
				fragment.deleteFragment(current);
				current = nu;
			});
		};
	}());

	// Users Admin resolvers
	var getUserListFragment = getPartFragments(userStorage, userListProps);
	var getRecentlyVisitedUsersFragment = function (userId) {
		var list = getDbRecordSet(userStorage, userId + '/recentlyVisited/users')
			.map(function (value) { return value.slice(1); });

		return getColFragments(list, getUserListFragment);
	};
	var getUsersAdminFragment = memoize(function () {
		var fragment = new FragmentGroup();
		// First page snapshot
		fragment.addFragment(getUserReducedData('views/usersAdmin'));
		// First page list data
		fragment.addFragment(getColFragments(getFirstPageItems(userStorage, 'usersAdmin'),
			getUserListFragment));
		return fragment;
	});

	// Official roles resolvers
	var getRecentlyVisitedBusinessProcessesFragment = function (userId, roleName) {
		var list = getDbRecordSet(userStorage,
			userId + '/recentlyVisited/businessProcesses/' + roleName)
			.map(function (value) { return value.slice(1); });

		return getColFragments(list, getBusinessProcessData);
	};
	var getBusinessProcessListFragment = getPartFragments(null,
		new Set(aFrom(bpListProps).concat(aFrom(bpListCompProps))));

	var getOfficialFragment = memoize(function (stepShortPath) {
		var fragment = new FragmentGroup()
		  , defaultStatusName = resolveDefaultStatus(stepShortPath);

		// To be visited (recently pending) business processes (full data)
		fragment.add(getColFragments(getFirstPageItems(reducedStorage, 'pendingBusinessProcesses/' +
			stepShortPath + '/' + defaultStatusName).toArray().slice(0, 10), getBusinessProcessData));
		// First page snapshot for each status
		fragment.addFragment(getReducedData('views/pendingBusinessProcesses/' + stepShortPath));
		// First page list data for each status
		fragment.addFragment(getColFragments(joinSets(
			keys(processingStepsMeta[stepShortPath]).map(function (status) {
				return getFirstPageItems(reducedStorage,
					'pendingBusinessProcesses/' + stepShortPath + '/' + status);
			})
		), getBusinessProcessListFragment));
		return fragment;
	}, { primitive: true });

	return memoize(function (appId) {
		var userId, roleName, shortRoleName, custom, fragment, initialBusinessProcesses, promise;

		appId = appId.split('.');
		userId = appId[0];
		roleName = appId[1];
		custom = appId[2];

		fragment = new FragmentGroup();

		if (!roleName) return fragment; // Temporary inconsistent state (client migration)

		// User profile data
		fragment.addFragment(getUserFragment(userId));
		// Sizes of pending files in official roles of user
		addOfficialStepsPendingSizes(userId, fragment);

		if (roleName === 'user') {
			if (custom) {
				// Business process application
				// Business process data
				fragment.addFragment(getBusinessProcessData(custom));
			} else {
				// My account
				// All businesss processes (full data)
				initialBusinessProcesses = getDbRecordSet(userStorage,
					userId + '/initialBusinessProcesses');
				promise = initialBusinessProcesses.promise;
				initialBusinessProcesses = initialBusinessProcesses
					.map(function (value) { return value.slice(1); });
				initialBusinessProcesses.promise = promise;
				// - All initial business processes
				fragment.addFragment(getColFragments(initialBusinessProcesses, getBusinessProcessData));
			}
			return fragment;
		}
		if (roleName === 'usersAdmin') {
			// Recently visited users (full data)
			fragment.addFragment(getRecentlyVisitedUsersFragment(userId));
			// Users Admin specific data
			fragment.addFragment(getUsersAdminFragment());
			return fragment;
		}
		if (roleName === 'metaAdmin') return fragment;

		if (isOfficialRoleName(roleName)) {
			shortRoleName = uncapitalize.call(roleName.slice('official'.length));
			// Recently visited business processes (full data)
			fragment.addFragment(getRecentlyVisitedBusinessProcessesFragment(userId, shortRoleName));
			// Official role specific data
			fragment.addFragment(getOfficialFragment(shortRoleName));
			return fragment;
		}
		console.error("\n\nError: Unrecognized role " + roleName + "\n\n");
		return fragment;
	}, { primitive: true });
};
<|endoftext|>


<commit_before>;
// 使用,避免连续代码

// UMD通用模块加载方式
(function(global, factory) {
    'use strict';
    if (typeof module !== 'undefined' && typeof module.exports === 'function') module.exports = factory();
    else if (typeof define !== 'undefined' && (define.amd || define.cmd)) define(factory);
    else global.ES5 = factory({});

})(this, function(ES5) {

    // 常用方法简写
    var ArrayPrototype = Array.prototype;
    var ObjectPrototype = Object.prototype;
    var FunctionPrototype = Function.prototype;
    var StringPrototype = String.prototype;
    var NumberPrototype = Number.prototype;

    var array_slice = ArrayPrototype.slice;
    var array_splice = ArrayPrototype.splice;
    var array_push = ArrayPrototype.push;
    var array_unshift = ArrayPrototype.unshift;
    var array_concat = ArrayPrototype.concat;
    var array_join = ArrayPrototype.join;

    var call = FunctionPrototype.call;
    var apply = FunctionPrototype.apply;

    var to_string = ObjectPrototype.toString;

    var max = Math.max;
    var min = Math.min;

    /**********************************/
    /*           Global               */
    /**********************************/

    // 检测是否支持对象描述符
    var isSupportDescriptors = Object.defineProperty && (function() {
        try {
            var obj = {};
            Object.defineProperty(obj, 'o', {
                enumerable: false, // 不可枚举的
                value: obj
            })

            for (var key in obj) {
                return false; // 如果进入循环，说明描述符不起作用，则是ES3
            }
            return obj.o === obj;
        } catch (e) {
            return false;
        }
    })();

    // 定义defineProperties方法，用此方法进行原型扩展
    var defineProperties　＝　(function() {
        var defineProperty;

        // 如果支持defineProperty
        defineProperty = isSupportDescriptors ? function(obj, key, value){
            if(key in obj) return;
            Object.defineProperty(obj, key, {
                key: value,
                configurable: true,
                enumerable: false,
                writable: true,
            })
        // else
        } : function(obj, key, value) {
            if(key in obj) return;
            obj[key] = value;
        }

        return function(obj, map) {
            for(var key in map) {
                if(Object.hasOwnProperty.call(map, key)) defineProperty(obj, key, map[key]);
            }
        }
    })();

    /**********************************/
    /*           Util                 */
    /**********************************/

    // important!! 由于这里只是模仿，所以就不将方法挂在原生对象的原型上了，直接挂在我们的自定义对象上，这样方便测试

    /**********************************/
    /*           Array                */
    /**********************************/

    ES5.Array = {};

    defineProperties(ES5.Array, {

        isArray: function(obj) {
            return to_string.call(object) === '[object Array]';
        },

        indexOf: function (arr, value, fromIndex) {
            // 用+号将字符串转为数字
            var i = +fromIndex || 0,
                len = arr.length;
            if(i > arr.length - 1) throw new TypeError('fromIndex muse less than arr.length');
            if(!arr || arr.length === 0) return -1;

            // 用while循环代替部分for循环,提升代码可读性
            while(i < len) {
                if(value === arr[i]) return i;
                i++;
            }
        },

        lastIndexOf: function(arr, value) {
            for (var i = arr.length - 1; i >= 0; i--) {
                if(value === arr[i]) return i;
            }
        },

        every: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn) throw new TypeError();

            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                // 利用!让所有不为真的返回值全部返回false
                if(!cb) return false;
            }
            return ture;
        },

        some: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn) throw new TypeError();

            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                // 只要能被转为ture的返回值，都返回ture;
                if(cb) return ture;
            }
            return false;
        },

        forEach: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn) throw new TypeError();

            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
            }
        },

        map: function(arr, fn/*, context*/ ) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn) throw new TypeError(fn.name + 'is not a function');

            var arr = new Array(arr.length);
            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                arr[i] = cb;
            }
            return arr;
        },
        filter: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn) throw new TypeError();

            var arr = [];
            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                if(cb) arr.push(arr[i]);
            }
            return arrrr;
        },
        reduce: function(arr, fn ,initialValue) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn) throw new TypeError();

            var previousValue;
            var initialSet = initialValue ? true : false;

            previousValue = initialValue;
            for (var i = 0, len = arr.length; i < len; i++) {
                if(initialSet) {
                    previousValue = fn.call(undefined, previousValue, arr[i], i, arr);
                }else {
                    previousValue = arr[0];
                    initialSet = true;
                }
            }

            if (!initialSet) throw new TypeError('Reduce of empty array with no initial value');
            return previousValue;
        },
        /**
         * ES6-Array-shim
         * @type {Object}
         */
        form: function() {

        },

        of: function() {

        }
    })

    /**********************************/
    /*           Function             */
    /**********************************/
    ES5.Function = {};

    defineProperties(ES5.Function, {
        bind: function() {

        }
    })

    /**********************************/
    /*           Object               */
    /**********************************/
    ES5.Object = {};

    defineProperties(ES5.Object, {

        getPrototypeOf: function() {

        },

        getOwnPropertyDescriptor: function () {

        },

        getOwnPropertyNames: function() {

        },

        // !important
        create: function() {

        },

        defineProperty: function() {

        },

        // !important
        defineProperties: function() {

        },

        seal: function() {

        },

        freeze: function() {

        },

        isSealed: function() {

        },

        isFrozen: function() {

        },

        // !important
        keys: function() {

        },

        isExtensible: function() {

        }
    })

    /**********************************/
    /*           Date                 */
    /**********************************/

    /**********************************/
    /*           Number               */
    /**********************************/

    /**********************************/
    /*           String               */
    /**********************************/

    ES5.String = {};
    defineProperties(ES5.String, {

        trim: function() {

        }
    })

    /**********************************/
    /*           JSON               */
    /**********************************/
    ES5.JSON = {};
    defineProperties(ES5.JSON, {
        parse: function() {

        },

        stringify: function() {

        }
    })

    return ES5;
});<commit_msg>add some new method<commit_after>;
// 使用,避免连续代码

// UMD通用模块加载方式
(function(global, factory) {
    'use strict';
    if (typeof module !== 'undefined' && typeof module.exports === 'function') module.exports = factory();
    else if (typeof define !== 'undefined' && (define.amd || define.cmd)) define(factory);
    else global.ES5 = factory({});

})(this, function(ES5) {

    // 常用方法简写
    var ArrayPrototype = Array.prototype;
    var ObjectPrototype = Object.prototype;
    var FunctionPrototype = Function.prototype;
    var StringPrototype = String.prototype;
    var NumberPrototype = Number.prototype;

    var array_slice = ArrayPrototype.slice;
    var array_splice = ArrayPrototype.splice;
    var array_push = ArrayPrototype.push;
    var array_unshift = ArrayPrototype.unshift;
    var array_concat = ArrayPrototype.concat;
    var array_join = ArrayPrototype.join;

    var call = FunctionPrototype.call;
    var apply = FunctionPrototype.apply;

    var to_string = ObjectPrototype.toString;

    var max = Math.max;
    var min = Math.min;

    /**********************************/
    /*           Global               */
    /**********************************/

    // 检测是否支持对象描述符
    var isSupportDescriptors = Object.defineProperty && (function() {
        try {
            var obj = {};
            Object.defineProperty(obj, 'o', {
                enumerable: false, // 不可枚举的
                value: obj
            })

            for (var key in obj) {
                return false; // 如果进入循环，说明描述符不起作用，则是ES3
            }
            return obj.o === obj;
        } catch (e) {
            return false;
        }
    })();

    // 定义defineProperties方法，用此方法进行原型扩展
    var defineProperties　＝　(function() {
        var defineProperty;

        // 如果支持defineProperty
        defineProperty = isSupportDescriptors ? function(obj, key, value){
            if(key in obj) return;
            Object.defineProperty(obj, key, {
                key: value,
                configurable: true,
                enumerable: false,
                writable: true,
            })
        // else
        } : function(obj, key, value) {
            if(key in obj) return;
            obj[key] = value;
        }

        return function(obj, map) {
            for(var key in map) {
                if(Object.hasOwnProperty.call(map, key)) defineProperty(obj, key, map[key]);
            }
        }
    })();

    /**********************************/
    /*           Util                 */
    /**********************************/

    // important!! 由于这里只是模仿，所以就不将方法挂在原生对象的原型上了，直接挂在我们的自定义对象上，这样方便测试

    /**********************************/
    /*           Array                */
    /**********************************/

    ES5.Array = {};

    defineProperties(ES5.Array, {

        isArray: function(obj) {
            return to_string.call(object) === '[object Array]';
        },

        indexOf: function (arr, value, fromIndex) {
            // 用+号将字符串转为数字
            var i = +fromIndex || 0,
                len = arr.length;
            if(i > arr.length - 1) throw new TypeError('fromIndex muse less than arr.length');
            if(!arr || arr.length === 0) return -1;

            // 用while循环代替部分for循环,提升代码可读性
            while(i < len) {
                if(value === arr[i]) return i;
                i++;
            }
        },

        lastIndexOf: function(arr, value) {
            for (var i = arr.length - 1; i >= 0; i--) {
                if(value === arr[i]) return i;
            }
        },

        every: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn !== 'function') throw new TypeError();

            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                // 利用!让所有不为真的返回值全部返回false
                if(!cb) return false;
            }
            return ture;
        },

        some: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn !== 'function') throw new TypeError();

            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                // 只要能被转为ture的返回值，都返回ture;
                if(cb) return ture;
            }
            return false;
        },

        forEach: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn !== 'function') throw new TypeError();

            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
            }
        },

        map: function(arr, fn/*, context*/ ) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn !== 'function') throw new TypeError(fn.name + 'is not a function');

            var arr = new Array(arr.length);
            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                arr[i] = cb;
            }
            return arr;
        },
        filter: function(arr, fn/*, context*/) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn !== 'function') throw new TypeError();

            var arr = [];
            var context = arguments.length >= 3 ? arguments[2] : void 0;
            for (var i = 0, len = arr.length; i < len; i++) {
                var cb = fn.call(context, arr[i], i, arr);
                if(cb) arr.push(arr[i]);
            }
            return arrrr;
        },
        reduce: function(arr, fn ,initialValue) {
            // 用void 0代替arr
            if (arr === void 0 || arr === null) throw new TypeError();
            if (typeof fn !== 'function') throw new TypeError();

            var previousValue;
            var initialSet = initialValue ? true : false;

            previousValue = initialValue;
            for (var i = 0, len = arr.length; i < len; i++) {
                if(initialSet) {
                    previousValue = fn.call(undefined, previousValue, arr[i], i, arr);
                }else {
                    previousValue = arr[0];
                    initialSet = true;
                }
            }

            if (!initialSet) throw new TypeError('Reduce of empty array with no initial value');
            return previousValue;
        },
        /**
         * ES6-Array-shim
         * @type {Object}
         */

        // 不能直接传入参数，因为要保证from函数的length为1.
        from: function(arrlike/*[, mapFn[, thisArg]]*/) {
            if (arrlike === void 0 || arrlike === null) throw new TypeError();
            // 不直接判断参数主要为了避免传入的undefined等值时，无法判断是否传了参数
            var mapFn = arguments.length > 1 ? arguments[1] : void 0;
            if (typeof mapFn !== 'function') throw new TypeError();

            var thisArg = arguments.length > 2 ? arguments[2] : void 0;

            var newArr = array_slice.call(arrlike);
            return mapFn ? newArr : this.map(newArr, mapFn, thisArg);
        },

        of: function() {
            return array_slice.call(arguments);
        },

        copyWithin: function(arr, target, start/*, end*/) {
            if (arr === void 0 || arrlike === null) throw new TypeError();
            var len = arr.length;

            if (typeof target !== 'number') throw new TypeError();
            // 不直接判断参数主要为了避免传入的undefined等值时，无法判断是否传了参数
            var end = arguments.length > 3 ? arguments[3] : void 0;
            if (typeof start !== 'undefined' && typeof start !== 'number') throw new TypeError();
            if (typeof end !== 'undefined' && typeof end !== 'number') throw new TypeError();

            if(target > len) return;
            var to = target < 0 ? Math.max(target + len, 0) : Math.min(target, len - 1);

            !start && (start = 0);
            // start小于0时，不能超过数组第一位；start大于0时，不能超过数组长度
            var from = start < 0 ? Math.max(start + len, 0) : Math.min(start, len - 1);

            !end && (end = arr.length -1);
            // end小于0时，不能超过数组第一位；end大于0时，不能超过数组长度
            var final = end < 0 ? Math.max(end + len, 0) : Math.min(end, len - 1);

            // !!!
            var count = Math.min(final - from, len - to);

        }
    })

    /**********************************/
    /*           Function             */
    /**********************************/
    ES5.Function = {};

    defineProperties(ES5.Function, {
        bind: function() {

        }
    })

    /**********************************/
    /*           Object               */
    /**********************************/
    ES5.Object = {};

    defineProperties(ES5.Object, {

        getPrototypeOf: function() {

        },

        getOwnPropertyDescriptor: function () {

        },

        getOwnPropertyNames: function() {

        },

        // !important
        create: function() {

        },

        defineProperty: function() {

        },

        // !important
        defineProperties: function() {

        },

        seal: function() {

        },

        freeze: function() {

        },

        isSealed: function() {

        },

        isFrozen: function() {

        },

        // !important
        keys: function() {

        },

        isExtensible: function() {

        }
    })

    /**********************************/
    /*           Date                 */
    /**********************************/

    /**********************************/
    /*           Number               */
    /**********************************/

    /**********************************/
    /*           String               */
    /**********************************/

    ES5.String = {};
    defineProperties(ES5.String, {

        trim: function() {

        }
    })

    /**********************************/
    /*           JSON               */
    /**********************************/
    ES5.JSON = {};
    defineProperties(ES5.JSON, {
        parse: function() {

        },

        stringify: function() {

        }
    })

    return ES5;
});<|endoftext|>


<commit_before>var User = require('../models/User');

module.exports = {

  newsletterSignup: function(req, res) {
    if (!req.accepts('json')) { res.status(406).end(); }

    if (req.data.user.receiveNewsletter === false) {
      req.data.user.receiveNewsletter = true;
      req.data.user.save(function(err) {
        if (err) { return res.status(500).send(err); }

        res.end();
      });
    } else {
      res.status(304).end();
    }
  }

};<commit_msg>add stubbed MailController#sendInvites<commit_after>var User = require('../models/User');

module.exports = {

  newsletterSignup: function(req, res) {
    if (!req.accepts('json')) { res.status(406).end(); }

    if (req.data.user.receiveNewsletter === false) {
      req.data.user.receiveNewsletter = true;
      req.data.user.save(function(err) {
        if (err) { return res.status(500).send(err); }

        res.end();
      });
    } else {
      res.status(304).end();
    }
  },

  sendInvites: function(invites) {

  },

};<|endoftext|>


<commit_before>/* globals describe, it */
/* jshint node: true, esnext: false, expr: true */
/* jscs: disable */
'use strict';

var addonIndex = require('../index');
var expect = require('chai').expect;

describe('index', function() {
  describe('#exclusionFilter', function() {
    it('should return `true` if a file is in the blacklist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        blacklist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.true;
    });

    it('should return `false` if a file is not in the blacklist', function() {
      var name = 'pipe';
      var dummyRegex = new RegExp('.*');
      var lists = {
        blacklist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });

    it('should return `false` if a file is in the whitelist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        whitelist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });

    it('should return `false` if a file is in both the whitelist and blacklist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        blacklist: ['range'],
        whitelist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });

    it('should return `false` if both lists are empty', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {};
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });
  });

  describe('#generateWhitelist', function() {
    it('should return files to whitelist when both `only` and `expect` are defined' , function() {
      var dummyConfig = {
        only: ['range', 'pipe', 'inc', 'dec'],
        except: ['inc']
      };
      var expectedResult = ['range', 'pipe', 'dec'];
      var result = addonIndex.generateWhitelist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });

    it('should return files to whitelist when `only` is defined' , function() {
      var dummyConfig = {
        only: ['range', 'pipe', 'inc', 'dec']
      };
      var expectedResult = ['range', 'pipe', 'inc', 'dec'];
      var result = addonIndex.generateWhitelist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });
  });

  describe('#generateBlacklist', function() {
    it('should return files to blacklist when both `only` and `expect` are defined' , function() {
      var dummyConfig = {
        only: ['range', 'pipe', 'inc', 'dec'],
        except: ['inc']
      };
      var expectedResult = ['inc'];
      var result = addonIndex.generateBlacklist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });

    it('should return files to blacklist when `except` is defined' , function() {
      var dummyConfig = {
        except: ['range', 'pipe', 'inc', 'dec']
      };
      var expectedResult = ['range', 'pipe', 'inc', 'dec'];
      var result = addonIndex.generateBlacklist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });
  });
});
/* jscs:enable */
<commit_msg>Auto merge of #61 - DockYard:add-index-test, r=poteto<commit_after>/* globals describe, it */
/* jshint node: true, esnext: false, expr: true */
/* jscs: disable */
'use strict';

var addonIndex = require('../index');
var expect = require('chai').expect;

describe('index', function() {
  describe('#exclusionFilter', function() {
    it('should return `true` if a file is in the blacklist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        blacklist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.true;
    });

    it('should return `false` if a file is not in the blacklist', function() {
      var name = 'pipe';
      var dummyRegex = new RegExp('.*');
      var lists = {
        blacklist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });

    it('should return `false` if a file is in the whitelist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        whitelist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });

    it('should return `true` if a file is not in the whitelist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        whitelist: ['inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.true;
    });

    it('should return `false` if a file is in both the whitelist and blacklist', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {
        blacklist: ['range'],
        whitelist: ['range', 'inc']
      };
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });

    it('should return `false` if both lists are empty', function() {
      var name = 'range';
      var dummyRegex = new RegExp('.*');
      var lists = {};
      var result = addonIndex.exclusionFilter(name, dummyRegex, lists);

      expect(result).to.be.false;
    });
  });

  describe('#generateWhitelist', function() {
    it('should return files to whitelist when both `only` and `expect` are defined' , function() {
      var dummyConfig = {
        only: ['range', 'pipe', 'inc', 'dec'],
        except: ['inc']
      };
      var expectedResult = ['range', 'pipe', 'dec'];
      var result = addonIndex.generateWhitelist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });

    it('should return files to whitelist when `only` is defined' , function() {
      var dummyConfig = {
        only: ['range', 'pipe', 'inc', 'dec']
      };
      var expectedResult = ['range', 'pipe', 'inc', 'dec'];
      var result = addonIndex.generateWhitelist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });
  });

  describe('#generateBlacklist', function() {
    it('should return files to blacklist when both `only` and `expect` are defined' , function() {
      var dummyConfig = {
        only: ['range', 'pipe', 'inc', 'dec'],
        except: ['inc']
      };
      var expectedResult = ['inc'];
      var result = addonIndex.generateBlacklist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });

    it('should return files to blacklist when `except` is defined' , function() {
      var dummyConfig = {
        except: ['range', 'pipe', 'inc', 'dec']
      };
      var expectedResult = ['range', 'pipe', 'inc', 'dec'];
      var result = addonIndex.generateBlacklist(dummyConfig);

      expect(result).to.eql(expectedResult);
    });
  });
});
/* jscs:enable */
<|endoftext|>


<commit_before>const { PanelTestProvider } = ChromeUtils.import(
  "resource://activity-stream/lib/PanelTestProvider.jsm"
);
const { ToolbarBadgeHub } = ChromeUtils.import(
  "resource://activity-stream/lib/ToolbarBadgeHub.jsm"
);

add_task(async function test_setup() {
  // Cleanup pref value because we click the fxa accounts button.
  // This is not required during tests because we "force show" the message
  // by sending it directly to the Hub bypassing targeting.
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("identity.fxaccounts.toolbar.accessed");
  });
});

add_task(async function test_fxa_badge_shown_nodelay() {
  const [msg] = (await PanelTestProvider.getMessages()).filter(
    ({ id }) => id === "FXA_ACCOUNTS_BADGE"
  );

  Assert.ok(msg, "FxA test message exists");

  // Ensure we badge immediately
  msg.content.delay = undefined;

  let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  Assert.ok(
    !browserWindow.document
      .getElementById(msg.content.target)
      .querySelector(".toolbarbutton-badge")
      .classList.contains("feature-callout"),
    "Initially element is not badged"
  );

  ToolbarBadgeHub.registerBadgeNotificationListener(msg);

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  let newWin = await BrowserTestUtils.openNewBrowserWindow();
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  await BrowserTestUtils.closeWindow(newWin);
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  // Click the button and clear the badge
  let fxaButton = document.getElementById(msg.content.target);
  fxaButton.click();

  await BrowserTestUtils.waitForCondition(
    () =>
      !browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Button should no longer be badged"
  );
});

add_task(async function test_fxa_badge_shown_withdelay() {
  const [msg] = (await PanelTestProvider.getMessages()).filter(
    ({ id }) => id === "FXA_ACCOUNTS_BADGE"
  );

  Assert.ok(msg, "FxA test message exists");

  // Enough to trigger the setTimeout badging
  msg.content.delay = 1;

  let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  Assert.ok(
    !browserWindow.document
      .getElementById(msg.content.target)
      .querySelector(".toolbarbutton-badge")
      .classList.contains("feature-callout"),
    "Initially element is not badged"
  );

  ToolbarBadgeHub.registerBadgeNotificationListener(msg);

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  let newWin = await BrowserTestUtils.openNewBrowserWindow();
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  await BrowserTestUtils.closeWindow(newWin);
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  // Click the button and clear the badge
  let fxaButton = document.getElementById(msg.content.target);
  fxaButton.click();

  await BrowserTestUtils.waitForCondition(
    () =>
      !browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Button should no longer be badged"
  );
});
<commit_msg>Bug 1570745 - Fix failing mochitests due to CFR message reference from the wrong provider<commit_after>const { OnboardingMessageProvider } = ChromeUtils.import(
  "resource://activity-stream/lib/OnboardingMessageProvider.jsm"
);
const { ToolbarBadgeHub } = ChromeUtils.import(
  "resource://activity-stream/lib/ToolbarBadgeHub.jsm"
);

add_task(async function test_setup() {
  // Cleanup pref value because we click the fxa accounts button.
  // This is not required during tests because we "force show" the message
  // by sending it directly to the Hub bypassing targeting.
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("identity.fxaccounts.toolbar.accessed");
  });
});

add_task(async function test_fxa_badge_shown_nodelay() {
  const [msg] = (await OnboardingMessageProvider.getMessages()).filter(
    ({ id }) => id === "FXA_ACCOUNTS_BADGE"
  );

  Assert.ok(msg, "FxA test message exists");

  // Ensure we badge immediately
  msg.content.delay = undefined;

  let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
  // Click the button and clear the badge that occurs normally at startup
  let fxaButton = browserWindow.document.getElementById(msg.content.target);
  fxaButton.click();

  await BrowserTestUtils.waitForCondition(
    () =>
      !browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Initially element is not badged"
  );

  ToolbarBadgeHub.registerBadgeNotificationListener(msg);

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  let newWin = await BrowserTestUtils.openNewBrowserWindow();
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  await BrowserTestUtils.closeWindow(newWin);
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  // Click the button and clear the badge
  fxaButton = document.getElementById(msg.content.target);
  fxaButton.click();

  await BrowserTestUtils.waitForCondition(
    () =>
      !browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Button should no longer be badged"
  );
});

add_task(async function test_fxa_badge_shown_withdelay() {
  const [msg] = (await OnboardingMessageProvider.getMessages()).filter(
    ({ id }) => id === "FXA_ACCOUNTS_BADGE"
  );

  Assert.ok(msg, "FxA test message exists");

  // Enough to trigger the setTimeout badging
  msg.content.delay = 1;

  let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
  // Click the button and clear the badge that occurs normally at startup
  let fxaButton = browserWindow.document.getElementById(msg.content.target);
  fxaButton.click();

  await BrowserTestUtils.waitForCondition(
    () =>
      !browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Initially element is not badged"
  );

  ToolbarBadgeHub.registerBadgeNotificationListener(msg);

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  let newWin = await BrowserTestUtils.openNewBrowserWindow();
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  await BrowserTestUtils.waitForCondition(
    () =>
      browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Wait for element to be badged"
  );

  await BrowserTestUtils.closeWindow(newWin);
  browserWindow = Services.wm.getMostRecentWindow("navigator:browser");

  // Click the button and clear the badge
  fxaButton = document.getElementById(msg.content.target);
  fxaButton.click();

  await BrowserTestUtils.waitForCondition(
    () =>
      !browserWindow.document
        .getElementById(msg.content.target)
        .querySelector(".toolbarbutton-badge")
        .classList.contains("feature-callout"),
    "Button should no longer be badged"
  );
});
<|endoftext|>