<commit_before>// async cookies API polyfill for document contexts
if (self.document) (function() {
  const DISALLOWED_IN_COOKIE_NAME_RE = /[()<>@,;:\\""\/\[\]?={} \0-\x1f\x7f]|[^\x21-\x7e\u00A0-\uFFFD]/;
  const DISALLOWED_IN_COOKIE_VALUE_RE = /[^\x2D-\x3A\x21\x23-\x2B\x3C-\x5B\x5D-\x7E\u00A0-\uFFFD]/;
  // 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.log('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('=');
        if (c !== -1) {
          let n = 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) {
      name = String(name || '') || undefined;
      if (!name) throw new SyntaxError('Cookie name is required');
      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;
      if (options.expires != null && !isNaN(expiresAsNumber = Number(options.expires))) {
        maxAge = (expiresAsNumber - Date.now()) / 1000;
      } else {
        expires = String(options.expires || '') || undefined;
      }
      if (expires && expires.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "expires" attribute');
      }
      let domain = String(options.domain || '') || undefined;
      if (domain && domain.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "domain" attribute');
      }
      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_();
    }
    disconnect() {
      this.interests_ = null;
      this.snapshots_ = null;
      if (this.timer_) {
        clearTimeout(this.timer_);
        this.timer_ = null;
      }
    }
    schedule_() {
      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;
          }
        }
        this.timer_ = setTimeout(() => this.tick_().then(() => this.schedule_()), interval);
      });
    }
    async tick_() {
      this.timer_ = null;
      let reported = {};
      let observed = [];
      let allCookies = await cookieStore.getAll();
      let forceReport = false;
      this.interests_.forEach(interestEntry => {
        let {cookieStore, interests, cookies_} = interestEntry;
        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 = 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, cookieObserver);
    }
  };
  if (!self.cookieStore) self.cookieStore = new CookieStore(new AsyncCookieJar_(self.document));
  if (!self.CookieObserver) self.CookieObserver = CookieObserver;
})();
<commit_msg>console.info for Set-Cookie<commit_after>// async cookies API polyfill for document contexts
if (self.document) (function() {
  const DISALLOWED_IN_COOKIE_NAME_RE = /[()<>@,;:\\""\/\[\]?={} \0-\x1f\x7f]|[^\x21-\x7e\u00A0-\uFFFD]/;
  const DISALLOWED_IN_COOKIE_VALUE_RE = /[^\x2D-\x3A\x21\x23-\x2B\x3C-\x5B\x5D-\x7E\u00A0-\uFFFD]/;
  // 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('=');
        if (c !== -1) {
          let n = 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) {
      name = String(name || '') || undefined;
      if (!name) throw new SyntaxError('Cookie name is required');
      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;
      if (options.expires != null && !isNaN(expiresAsNumber = Number(options.expires))) {
        maxAge = (expiresAsNumber - Date.now()) / 1000;
      } else {
        expires = String(options.expires || '') || undefined;
      }
      if (expires && expires.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "expires" attribute');
      }
      let domain = String(options.domain || '') || undefined;
      if (domain && domain.indexOf(';') !== -1) {
        throw new SyntaxError('Character ";" is not allowed in cookie "domain" attribute');
      }
      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_();
    }
    disconnect() {
      this.interests_ = null;
      this.snapshots_ = null;
      if (this.timer_) {
        clearTimeout(this.timer_);
        this.timer_ = null;
      }
    }
    schedule_() {
      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;
          }
        }
        this.timer_ = setTimeout(() => this.tick_().then(() => this.schedule_()), interval);
      });
    }
    async tick_() {
      this.timer_ = null;
      let reported = {};
      let observed = [];
      let allCookies = await cookieStore.getAll();
      let forceReport = false;
      this.interests_.forEach(interestEntry => {
        let {cookieStore, interests, cookies_} = interestEntry;
        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 = 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, cookieObserver);
    }
  };
  if (!self.cookieStore) self.cookieStore = new CookieStore(new AsyncCookieJar_(self.document));
  if (!self.CookieObserver) self.CookieObserver = CookieObserver;
})();
<|endoftext|>


<commit_before>/**
 * @class C2
 * @description custom unit compressor with program-dependent action
 * @param {object} json parameters in JSON notation
 *                      { threshold, ratio, attack, release }
 */
WX.C2 = function(json) {
  // calling super constructor
  WX._Unit.call(this);
  // creating unit-specific properties
  Object.defineProperties(this, {
    _threshold: {
      enumerable: false,
      writable: true,
      value: WX.db2lin(-12.0)
    },
    _ratio: {
      enumerable: false,
      writable: true,
      value: 4.0
    },
    _iratio: {
      enumerable: false,
      writable: true,
      value: 1.0 / (this._ratio - 1.0)
    },
    _makeup: {
      enumerable: false,
      writable: true,
      value: WX.db2lin(0.0)
    },
    _detector: {
      enumerable: false,
      writable: false,
      value: new WX._Internals.DualLevelDetector()
    },
    _processor: {
      enumerable: false,
      writable: false,
      value: WX._context.createScriptProcessor(
          WX._customUnitBufferSize, 2, 2
        )
    }
  });
  var me = this;
  this._processor.onaudioprocess = function(event) {
    me._callback(event);
  };
  // performing unit-specific actions
  this._inlet.connect(this._processor);
  this._processor.connect(this._outlet);
  // assign (default) parameters
  this.params = json;
};

WX.C2.prototype = Object.create(WX._Unit.prototype, {

  /**
   * callback for onaudioprocess
   * @param {object} event
   */
  _callback: {
    value: function(event) {
      // temp vars
      var level, leveldB, gain, gaindB, delta;
      // input and output array buffer
      var inputL = event.inputBuffer.getChannelData(0);
      var inputR = event.inputBuffer.getChannelData(1);
      var outputL = event.outputBuffer.getChannelData(0);
      var outputR = event.outputBuffer.getChannelData(1);
      // callback loop
      for (var i = 0, b = WX._customUnitBufferSize; i < b; ++i) {
        // get abs-max from stereo
        var s = Math.max(Math.abs(inputL[i]), Math.abs(inputR[i]));
        // peak detection
        level = this._detector.process(s);
        // gain computer: compression with ratio
        delta = WX.lin2db(level / this._threshold);
        gaindB = (delta <= 0.0) ? 0.0 : delta * iratio;
        // gain computer: limiting
        // gaindB = Math.min(0.0, WX.lin2db(this._threshold / level));
        // get linear gain
        gain = WX.db2lin(gaindB);
        // compute output
        outputL[i] = inputL[i] * gain * this._makeup;
        outputR[i] = inputR[i] * gain * this._makeup;
      }
    }
  },

  /**
   * get/set threshold
   * @param {float} dB for user, linear for internal processing
   */
  threshold: {
    enumerable: true,
    get: function() {
      return WX.lin2db(this._threshold);
    },
    set: function(value) {
      this._threshold = WX.db2lin(value);
    }
  },

  /**
   * get/set ratio
   * @param {float} value arbitrary ratio
   */
  ratio: {
    enumerable: true,
    get: function() {
      return this._ratio;
    },
    set: function(value) {
      this._ratio = value;
      this._iratio = 1.0 / (this._ratio - 1.0);
    }
  },

  /**
   * get/set attack
   * @param {float} value attack in seconds
   */
  attack: {
    enumerable: true,
    get: function() {
      return this._detector.attack;
    },
    set: function(value) {
      this._detector.attack = value;
    }
  },

  /**
   * get/set release
   * @param {float} value release in seconds
   */
  release: {
    enumerable: true,
    get: function() {
      return this._detector.release;
    },
    set: function(value) {
      this._detector.release = value;
    }
  }
});<commit_msg>patched C2 ratio bug<commit_after>/**
 * @class C2
 * @description custom unit compressor with program-dependent action
 * @param {object} json parameters in JSON notation
 *                      { threshold, ratio, attack, release }
 */
WX.C2 = function(json) {
  // calling super constructor
  WX._Unit.call(this);
  // creating unit-specific properties
  Object.defineProperties(this, {
    _threshold: {
      enumerable: false,
      writable: true,
      value: WX.db2lin(-12.0)
    },
    _ratio: {
      enumerable: false,
      writable: true,
      value: 4.0
    },
    _iratio: {
      enumerable: false,
      writable: true,
      value: 1.0 / (this._ratio - 1.0)
    },
    _makeup: {
      enumerable: false,
      writable: true,
      value: WX.db2lin(0.0)
    },
    _detector: {
      enumerable: false,
      writable: false,
      value: new WX._Internals.DualLevelDetector()
    },
    _processor: {
      enumerable: false,
      writable: false,
      value: WX._context.createScriptProcessor(
          WX._customUnitBufferSize, 2, 2
        )
    }
  });
  var me = this;
  this._processor.onaudioprocess = function(event) {
    me._callback(event);
  };
  // performing unit-specific actions
  this._inlet.connect(this._processor);
  this._processor.connect(this._outlet);
  // assign (default) parameters
  this.params = json;
};

WX.C2.prototype = Object.create(WX._Unit.prototype, {

  /**
   * callback for onaudioprocess
   * @param {object} event
   */
  _callback: {
    value: function(event) {
      // temp vars
      var level, leveldB, gain, gaindB, delta;
      // input and output array buffer
      var inputL = event.inputBuffer.getChannelData(0);
      var inputR = event.inputBuffer.getChannelData(1);
      var outputL = event.outputBuffer.getChannelData(0);
      var outputR = event.outputBuffer.getChannelData(1);
      // callback loop
      for (var i = 0, b = WX._customUnitBufferSize; i < b; ++i) {
        // get abs-max from stereo
        var s = Math.max(Math.abs(inputL[i]), Math.abs(inputR[i]));
        // peak detection
        level = this._detector.process(s);
        // gain computer: compression with ratio
        delta = WX.lin2db(level / this._threshold);
        gaindB = (delta <= 0.0) ? 0.0 : delta * this._iratio;
        // gain computer: limiting
        // gaindB = Math.min(0.0, WX.lin2db(this._threshold / level));
        // get linear gain
        gain = WX.db2lin(gaindB);
        // compute output
        outputL[i] = inputL[i] * gain * this._makeup;
        outputR[i] = inputR[i] * gain * this._makeup;
      }
    }
  },

  /**
   * get/set threshold
   * @param {float} dB for user, linear for internal processing
   */
  threshold: {
    enumerable: true,
    get: function() {
      return WX.lin2db(this._threshold);
    },
    set: function(value) {
      this._threshold = WX.db2lin(value);
    }
  },

  /**
   * get/set ratio
   * @param {float} value arbitrary ratio
   */
  ratio: {
    enumerable: true,
    get: function() {
      return this._ratio;
    },
    set: function(value) {
      this._ratio = value;
      this._iratio = 1.0 / (this._ratio - 1.0);
    }
  },

  /**
   * get/set attack
   * @param {float} value attack in seconds
   */
  attack: {
    enumerable: true,
    get: function() {
      return this._detector.attack;
    },
    set: function(value) {
      this._detector.attack = value;
    }
  },

  /**
   * get/set release
   * @param {float} value release in seconds
   */
  release: {
    enumerable: true,
    get: function() {
      return this._detector.release;
    },
    set: function(value) {
      this._detector.release = value;
    }
  }
});<|endoftext|>


<commit_before>var co = require('co');
var extend = require('extend-merge').extend;
var merge = require('extend-merge').merge;
var Model = require('chaos-orm').Model;
var Collector = require('chaos-orm').Collector;

/**
 * The Query wrapper.
 */
class Query {

  /**
   * Gets/sets classes dependencies.
   *
   * @param  Object classes The classes dependencies to set or none to get it.
   * @return mixed          The classes dependencies.
   */
  static classes(classes) {
    if (arguments.length) {
      this._classes = extend({}, this._classes, classes);
    }
    return this._classes;
  }

  /**
   * Creates a new record object with default values.
   *
   * @param array config Possible options are:
   *                      - `'model'`  _Function_ : A model class.
   *                      - `'schema'` _Object_   : Alternatively a schema instance can be provided instead of the model.
   *                      - `'query'`  _Object_   : The query.
   */
  constructor(config) {
    var defaults = {
      model: undefined,
      schema: undefined,
      query: {}
    };
    config = extend({}, defaults, config);

    if (config.model) {
      this._model = config.model;
      this._schema = this._model.definition();
    } else {
      this._schema = config.schema;
    }

    /**
     * Count the number of identical aliases in a query for building unique aliases.
     *
     * @var Object
     */
    this._aliasCounter = {};

    /**
     * Map beetween relation pathsand corresponding aliases.
     *
     * @var Object
     */
    this._aliases = {};

    /**
     * Map beetween generated aliases and corresponding schema.
     *
     * @var Object
     */
    this._schemas = {};

    /**
     * The relations to include.
     *
     * @var Array
     */
    this._embed = [];

    /**
     * Some conditions over some relations.
     *
     * @var Array
     */
    this._has = [];

    /**
     * Pagination.
     *
     * @var Array
     */
    this._page = [];

    var schema = this.schema();

    /**
     * The select statement instance.
     *
     * @var Function
     */
    this._statement = schema.connection().dialect().statement('select');

    var source = schema.source();
    var from = {};
    from[source] = this.alias('', schema);
    this.statement().from(from);

    for (var key in config.query) {
      this[key](config.query[key]);
    }
  }

  /**
   * Gets the schema.
   *
   * @return Function Returns the schema.
   */
  schema() {
    if (!this._schema) {
      throw new Error("Error, missing schema for this query.");
    }
    return this._schema;
  }

  /**
   * Gets the model.
   *
   * @return Function Returns the model.
   */
  model() {
    return this._model;
  }

  /**
   * Gets the query statement.
   *
   * @return Function Returns a connection instance.
   */
  statement() {
    return this._statement;
  }

  /**
   * Executes the query and returns the result.
   *
   * @param  Object  Options The fetching options.
   * @return Promise         A Promise.
   */
  get(options) {
    return co(function*(){
      var defaults = {
        collector: undefined,
        return: 'entity'
      };
      options = extend({}, defaults, options);

      var classname = this.constructor.classes().collector;
      var collector = options.collector = options.collector ? options.collector : new classname();

      this._applyHas();
      this._applyLimit();

      var noFields = !this.statement().data('fields').length;
      if (noFields) {
        var star = {};
        star[this.alias()] = ['*'];
        this.statement().fields([star]);
      }

      var collection;
      var ret = options['return'];

      var schema = this.schema();
      var cursor = yield schema.connection().query(this.statement().toString(this._schemas));

      switch (ret) {
        case 'entity':
          var source = schema.source();
          var key = schema.key();

          var model = this.model();
          if (!model) {
            throw new Error("Missing model for this query, set `'return'` to `'object'` to get row data.");
          }

          collection = model.create([], {
            collector: collector,
            type: 'set',
            exists: true
          });

          if (this.statement().data('limit')) {
            var count = this.count();
            collection.meta({ count: yield count });
          }

          for (var record of cursor) {
            var uuid = source + ':' + record[key];
            if (record[key] && collector.has(uuid)) {
              collection.push(collector.get(uuid));
            } else {
              collection.push(model.create(record, {
                collector: collector,
                exists: noFields ? true : null
              }));
            }
          }
          break;
        case 'array':
        case 'object':
          collection = [];
          for (var record of cursor) {
            collection.push(extend({}, record));
          }
          break;
        default:
          throw new Error("Invalid `'" + options['return'] + "'` mode as `'return'` value");
          break;
      }
      return schema.embed(collection, this._embed, { fetchOptions: options });
    }.bind(this));
  }

  /**
   * Alias for `get()`
   *
   * @return object An iterator instance.
   */
  all(options) {
    return this.get(options);
  }

  /**
   * Executes the query and returns the first result only.
   *
   * @return object An entity instance.
   */
  first(options) {
    return co(function*() {
      var result = yield this.get(options);
      return Array.isArray(result) ? result[0] : result.get(0);
    }.bind(this));
  }

  /**
   * Executes the query and returns the count number.
   *
   * @return integer The number of rows in result.
   */
  count() {
    return co(function*() {
      var statement = this.statement();
      var counter = this.schema().connection().dialect().statement('select');
      counter.fields({':plain': 'COUNT(*)'});
      counter.data('from', statement.data('from'));
      counter.data('joins', statement.data('joins'));
      counter.data('where', statement.data('where'));
      counter.data('group', statement.data('group'));
      counter.data('having', statement.data('having'));
      var cursor = yield this.schema().connection().query(counter.toString());
      var result = cursor.next();
      var key = Object.keys(result)[0]
      return Number.parseInt(result[key]);
    }.bind(this));
  }

  /**
   * Adds some fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  fields(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);

    var schema = this.schema();

    for (var value of fields) {
      if (typeof value === 'string' && schema.has(value)) {
        var aliased = {};
        aliased[this.alias()] = [value]
        this.statement().fields(aliased);
      } else {
        this.statement().fields(value);
      }
    }
    return this;
  }

  /**
   * Adds some where conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  where(conditions, alias) {
    var conditions = this.statement().dialect().prefix(conditions, alias ? alias : this.alias(), false);
    this.statement().where(conditions);
    return this;
  }

  /**
   * Alias for `where()`.
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  conditions(conditions) {
    return this.where(conditions);
  }

  /**
   * Adds some group by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  group(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().group(fields);
    return this;
  }

  /**
   * Adds some having conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  having(conditions) {
    conditions = this.statement().dialect().prefix(conditions, this.alias());
    this.statement().having(conditions);
    return this;
  }

  /**
   * Adds some order by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  order(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().order(fields);
    return this;
  }

  /**
   * Sets page number.
   *
   * @param  integer page The page number
   * @return self
   */
  page(page)
  {
    this._page.page = page;
    return this;
  }

  /**
   * Sets offset value.
   *
   * @param  integer offset The offset value.
   * @return self
   */
  offset(offset)
  {
    this._page.offset = offset;
    return this;
  }

  /**
   * Sets limit value.
   *
   * @param  integer limit The number of results to limit or `0` for limit at all.
   * @return self
   */
  limit(limit)
  {
    this._page.limit = Number.parseInt(limit);
    return this;
  }

  /**
   * Applies a query handler
   *
   * @param  Closure  closure A closure.
   * @return Function         Returns `this`.
   */
  handler(closure) {
    if (typeof closure === 'function') {
      closure(this);
    }
    return this;
  }

  /**
   * Sets the relations to retrieve.
   *
   * @param  array  embed The relations to load with the query.
   * @return object        Returns `this`.
   */
  embed(embed, conditions) {
    if (!arguments.length) {
      return this._embed;
    }
    if (typeof embed === "string" && arguments.length === 2) {
      var mix = {};
      mix[embed] = conditions || [];
      embed = [mix];
    } else {
      embed = Array.isArray(embed) ? embed : [embed];
    }
    this._embed = this._embed.concat(embed);
    return this;
  }

  /**
   * Sets the conditionnal dependency over some relations.
   *
   * @param array The conditionnal dependency.
   */
  has(has, conditions) {
    if (!arguments.length) {
      return this._has;
    }
    if (typeof has === "string" && arguments.length === 2) {
      var mix = {};
      mix[has] = conditions || [];
      has = [mix];
    }
    this._has = this._has.concat(has)
    return this;
  }

  /**
   * Gets a unique alias for the query or a query's relation if `relpath` is set.
   *
   * @param  string path   A dotted relation name or for identifying the query's relation.
   * @param  object schema The corresponding schema to alias.
   * @return string        A string alias.
   */
  alias(path, schema) {
    if (arguments.length < 2) {
      path = path || '';
      if (this._aliases[path] !== undefined) {
        return this._aliases[path];
      } else {
        throw new Error("No alias has been defined for `'" + path + "'`.");
      }
    }

    var alias = schema.source();
    if (this._aliasCounter[alias] === undefined) {
      this._aliasCounter[alias] = 0;
      this._aliases[path] = alias;
    } else {
      alias = this._aliases[path] = alias + '__' + this._aliasCounter[alias]++;
    }
    this._schemas[alias] = schema;
    return alias;
  }

  /**
   * Applies the has conditions.
   */
  _applyHas() {
    var schema = this.schema();
    var tree = schema.treeify(this.has());
    this._applyJoins(this.schema(), tree, '', this.alias());
    var has = this.has();
    for (var value of has) {
      var key = Object.keys(value)[0];
      var conditions = value[key];
      this.where(conditions, this.alias(key));
    }
  }

  /**
   * Applies the limit range when applicable.
   */
  _applyLimit() {
    if (!this._page.limit) {
        return;
    }
    var offset;
    if (this._page.offset) {
      offset = this._page.offset;
    } else {
      var page = this._page.page ? this._page.page : 1;
      offset = (page - 1) * this._page.limit;
    }
    this.statement().limit(this._page.limit, offset);
  }

  /**
   * Applies joins.
   *
   * @param Object schema    The schema to perform joins on.
   * @param Array  tree      The tree of relations to join.
   * @param Array  basePath  The base relation path.
   * @param String aliasFrom The alias name of the from model.
   */
  _applyJoins(schema, tree, basePath, aliasFrom) {
    for (var key in tree) {
      var childs = tree[key];
      var rel = schema.relation(key);
      var path = basePath ? basePath + '.' + key : key;
      var to;

      if (rel.type() !== 'hasManyThrough') {
        to = this._join(path, rel, aliasFrom);
      } else {
        var name = rel.using();
        var nameThrough = rel.through();
        var pathThrough = path ? path + '.' + nameThrough : nameThrough;
        var from = rel.from();

        var relThrough = from.definition().relation(nameThrough);
        var aliasThrough = this._join(pathThrough, relThrough, aliasFrom);

        var modelThrough = relThrough.to();
        var relTo = modelThrough.definition().relation(name);
        to = this._join(path, relTo, aliasThrough);
      }

      if (childs && Object.keys(childs).length) {
        this._applyJoins(rel.to().definition(), childs, path, to);
      }
    }
  }

  /**
   * Set a query's join according a Relationship.
   *
   * @param  string path      The relation path.
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @return string           The "to" model alias.
   */
  _join(path, rel, fromAlias) {
    if (this._aliases[path] !== undefined) {
      return this._aliases[path];
    }

    var schema = rel.to().definition();
    var source = schema.source();
    var toAlias = this.alias(path, schema);

    var table = {};
    table[source] = toAlias;

    this.statement().join(
      table,
      this._on(rel, fromAlias, toAlias),
      'LEFT'
    );
    return toAlias;
  }

  /**
   * Build the `ON` constraints from a `Relationship` instance.
   *
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @param  string toAlias   The "to" model alias.
   * @return array             A constraints array.
   */
  _on(rel, fromAlias, toAlias) {
    if (rel.type() === 'hasManyThrough') {
      return [];
    }
    var keys = rel.keys();
    var fromField = Object.keys(keys)[0];
    var toField = keys[fromField];
    return { '=': [{ ':name': fromAlias + '.' + fromField },  { ':name': toAlias + '.' + toField }] };
  }

  /**
   * Return the SQL string.
   *
   * @return String
   */
  toString() {
    var statement = this._statement;
    var parts = {
      fields: statement.data('fields').slice(),
      joins: statement.data('joins').slice(),
      where: statement.data('where').slice(),
      limit: statement.data('limit')
    };
    this._applyHas();
    this._applyLimit();

    var noFields = !statement.data('fields');
    if (noFields) {
      statement.fields({ [this.alias()]: ['*'] });
    }
    var sql = statement.toString(this._schemas);

    for (var name in parts) {
      statement.data(name, parts[name]);
    }
    this._aliasCounter = {};
    this._aliases = {};
    this.alias('', this.schema());
    return sql;
  }
}

/**
 * Class dependencies.
 *
 * @var array
 */
Query._classes = {
  collector: Collector
};

module.exports = Query;
<commit_msg>Remove collector managment from drivers.<commit_after>var co = require('co');
var extend = require('extend-merge').extend;
var merge = require('extend-merge').merge;
var Model = require('chaos-orm').Model;

/**
 * The Query wrapper.
 */
class Query {

  /**
   * Gets/sets classes dependencies.
   *
   * @param  Object classes The classes dependencies to set or none to get it.
   * @return mixed          The classes dependencies.
   */
  static classes(classes) {
    if (arguments.length) {
      this._classes = extend({}, this._classes, classes);
    }
    return this._classes;
  }

  /**
   * Creates a new record object with default values.
   *
   * @param array config Possible options are:
   *                      - `'model'`  _Function_ : A model class.
   *                      - `'schema'` _Object_   : Alternatively a schema instance can be provided instead of the model.
   *                      - `'query'`  _Object_   : The query.
   */
  constructor(config) {
    var defaults = {
      model: undefined,
      schema: undefined,
      query: {}
    };
    config = extend({}, defaults, config);

    if (config.model) {
      this._model = config.model;
      this._schema = this._model.definition();
    } else {
      this._schema = config.schema;
    }

    /**
     * Count the number of identical aliases in a query for building unique aliases.
     *
     * @var Object
     */
    this._aliasCounter = {};

    /**
     * Map beetween relation pathsand corresponding aliases.
     *
     * @var Object
     */
    this._aliases = {};

    /**
     * Map beetween generated aliases and corresponding schema.
     *
     * @var Object
     */
    this._schemas = {};

    /**
     * The relations to include.
     *
     * @var Array
     */
    this._embed = [];

    /**
     * Some conditions over some relations.
     *
     * @var Array
     */
    this._has = [];

    /**
     * Pagination.
     *
     * @var Array
     */
    this._page = [];

    var schema = this.schema();

    /**
     * The select statement instance.
     *
     * @var Function
     */
    this._statement = schema.connection().dialect().statement('select');

    var source = schema.source();
    var from = {};
    from[source] = this.alias('', schema);
    this.statement().from(from);

    for (var key in config.query) {
      if (typeof this[key] !== 'function') {
        throw new Error("Invalid option `'" + key + "'` as query options.");
      }
      this[key](config.query[key]);
    }
  }

  /**
   * Gets the schema.
   *
   * @return Function Returns the schema.
   */
  schema() {
    if (!this._schema) {
      throw new Error("Error, missing schema for this query.");
    }
    return this._schema;
  }

  /**
   * Gets the model.
   *
   * @return Function Returns the model.
   */
  model() {
    return this._model;
  }

  /**
   * Gets the query statement.
   *
   * @return Function Returns a connection instance.
   */
  statement() {
    return this._statement;
  }

  /**
   * Executes the query and returns the result.
   *
   * @param  Object  Options The fetching options.
   * @return Promise         A Promise.
   */
  get(options) {
    return co(function*(){
      var defaults = {
        return: 'entity'
      };
      options = extend({}, defaults, options);

      this._applyHas();
      this._applyLimit();

      var noFields = !this.statement().data('fields').length;
      if (noFields) {
        var star = {};
        star[this.alias()] = ['*'];
        this.statement().fields([star]);
      }

      var collection;
      var ret = options['return'];

      var schema = this.schema();
      var cursor = yield schema.connection().query(this.statement().toString(this._schemas));

      switch (ret) {
        case 'entity':
          var model = this.model();
          if (!model) {
            throw new Error("Missing model for this query, set `'return'` to `'object'` to get row data.");
          }

          collection = model.create([], {
            type: 'set',
            exists: true
          });

          if (this.statement().data('limit')) {
            var count = this.count();
            collection.meta({ count: yield count });
          }

          for (var record of cursor) {
            collection.push(model.create(record, {
              exists: noFields ? true : null
            }));
          }
          break;
        case 'array':
        case 'object':
          collection = [];
          for (var record of cursor) {
            collection.push(extend({}, record));
          }
          break;
        default:
          throw new Error("Invalid `'" + options['return'] + "'` mode as `'return'` value");
          break;
      }
      return schema.embed(collection, this._embed, { fetchOptions: options });
    }.bind(this));
  }

  /**
   * Alias for `get()`
   *
   * @return object An iterator instance.
   */
  all(options) {
    return this.get(options);
  }

  /**
   * Executes the query and returns the first result only.
   *
   * @return object An entity instance.
   */
  first(options) {
    return co(function*() {
      var result = yield this.get(options);
      return Array.isArray(result) ? result[0] : result.get(0);
    }.bind(this));
  }

  /**
   * Executes the query and returns the count number.
   *
   * @return integer The number of rows in result.
   */
  count() {
    return co(function*() {
      var statement = this.statement();
      var counter = this.schema().connection().dialect().statement('select');
      counter.fields({':plain': 'COUNT(*)'});
      counter.data('from', statement.data('from'));
      counter.data('joins', statement.data('joins'));
      counter.data('where', statement.data('where'));
      counter.data('group', statement.data('group'));
      counter.data('having', statement.data('having'));
      var cursor = yield this.schema().connection().query(counter.toString());
      var result = cursor.next();
      var key = Object.keys(result)[0]
      return Number.parseInt(result[key]);
    }.bind(this));
  }

  /**
   * Adds some fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  fields(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);

    var schema = this.schema();

    for (var value of fields) {
      if (typeof value === 'string' && schema.has(value)) {
        var aliased = {};
        aliased[this.alias()] = [value]
        this.statement().fields(aliased);
      } else {
        this.statement().fields(value);
      }
    }
    return this;
  }

  /**
   * Adds some where conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  where(conditions, alias) {
    var conditions = this.statement().dialect().prefix(conditions, alias ? alias : this.alias(), false);
    this.statement().where(conditions);
    return this;
  }

  /**
   * Alias for `where()`.
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  conditions(conditions) {
    return this.where(conditions);
  }

  /**
   * Adds some group by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  group(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().group(fields);
    return this;
  }

  /**
   * Adds some having conditions to the query
   *
   * @param  mixed    conditions The conditions for this query.
   * @return Function            Returns `this`.
   */
  having(conditions) {
    conditions = this.statement().dialect().prefix(conditions, this.alias());
    this.statement().having(conditions);
    return this;
  }

  /**
   * Adds some order by fields to the query
   *
   * @param  mixed    fields The fields.
   * @return Function        Returns `this`.
   */
  order(fields) {
    fields = Array.isArray(fields) && arguments.length === 1 ? fields : Array.prototype.slice.call(arguments);
    fields = this.statement().dialect().prefix(fields, this.alias());
    this.statement().order(fields);
    return this;
  }

  /**
   * Sets page number.
   *
   * @param  integer page The page number
   * @return self
   */
  page(page)
  {
    this._page.page = page;
    return this;
  }

  /**
   * Sets offset value.
   *
   * @param  integer offset The offset value.
   * @return self
   */
  offset(offset)
  {
    this._page.offset = offset;
    return this;
  }

  /**
   * Sets limit value.
   *
   * @param  integer limit The number of results to limit or `0` for limit at all.
   * @return self
   */
  limit(limit)
  {
    this._page.limit = Number.parseInt(limit);
    return this;
  }

  /**
   * Applies a query handler
   *
   * @param  Closure  closure A closure.
   * @return Function         Returns `this`.
   */
  handler(closure) {
    if (typeof closure === 'function') {
      closure(this);
    }
    return this;
  }

  /**
   * Sets the relations to retrieve.
   *
   * @param  array  embed The relations to load with the query.
   * @return object        Returns `this`.
   */
  embed(embed, conditions) {
    if (!arguments.length) {
      return this._embed;
    }
    if (typeof embed === "string" && arguments.length === 2) {
      var mix = {};
      mix[embed] = conditions || [];
      embed = [mix];
    } else {
      embed = Array.isArray(embed) ? embed : [embed];
    }
    this._embed = this._embed.concat(embed);
    return this;
  }

  /**
   * Sets the conditionnal dependency over some relations.
   *
   * @param array The conditionnal dependency.
   */
  has(has, conditions) {
    if (!arguments.length) {
      return this._has;
    }
    if (typeof has === "string" && arguments.length === 2) {
      var mix = {};
      mix[has] = conditions || [];
      has = [mix];
    }
    this._has = this._has.concat(has)
    return this;
  }

  /**
   * Gets a unique alias for the query or a query's relation if `relpath` is set.
   *
   * @param  string path   A dotted relation name or for identifying the query's relation.
   * @param  object schema The corresponding schema to alias.
   * @return string        A string alias.
   */
  alias(path, schema) {
    if (arguments.length < 2) {
      path = path || '';
      if (this._aliases[path] !== undefined) {
        return this._aliases[path];
      } else {
        throw new Error("No alias has been defined for `'" + path + "'`.");
      }
    }

    var alias = schema.source();
    if (this._aliasCounter[alias] === undefined) {
      this._aliasCounter[alias] = 0;
      this._aliases[path] = alias;
    } else {
      alias = this._aliases[path] = alias + '__' + this._aliasCounter[alias]++;
    }
    this._schemas[alias] = schema;
    return alias;
  }

  /**
   * Applies the has conditions.
   */
  _applyHas() {
    var schema = this.schema();
    var tree = schema.treeify(this.has());
    this._applyJoins(this.schema(), tree, '', this.alias());
    var has = this.has();
    for (var value of has) {
      var key = Object.keys(value)[0];
      var conditions = value[key];
      this.where(conditions, this.alias(key));
    }
  }

  /**
   * Applies the limit range when applicable.
   */
  _applyLimit() {
    if (!this._page.limit) {
        return;
    }
    var offset;
    if (this._page.offset) {
      offset = this._page.offset;
    } else {
      var page = this._page.page ? this._page.page : 1;
      offset = (page - 1) * this._page.limit;
    }
    this.statement().limit(this._page.limit, offset);
  }

  /**
   * Applies joins.
   *
   * @param Object schema    The schema to perform joins on.
   * @param Array  tree      The tree of relations to join.
   * @param Array  basePath  The base relation path.
   * @param String aliasFrom The alias name of the from model.
   */
  _applyJoins(schema, tree, basePath, aliasFrom) {
    for (var key in tree) {
      var childs = tree[key];
      var rel = schema.relation(key);
      var path = basePath ? basePath + '.' + key : key;
      var to;

      if (rel.type() !== 'hasManyThrough') {
        to = this._join(path, rel, aliasFrom);
      } else {
        var name = rel.using();
        var nameThrough = rel.through();
        var pathThrough = path ? path + '.' + nameThrough : nameThrough;
        var from = rel.from();

        var relThrough = from.definition().relation(nameThrough);
        var aliasThrough = this._join(pathThrough, relThrough, aliasFrom);

        var modelThrough = relThrough.to();
        var relTo = modelThrough.definition().relation(name);
        to = this._join(path, relTo, aliasThrough);
      }

      if (childs && Object.keys(childs).length) {
        this._applyJoins(rel.to().definition(), childs, path, to);
      }
    }
  }

  /**
   * Set a query's join according a Relationship.
   *
   * @param  string path      The relation path.
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @return string           The "to" model alias.
   */
  _join(path, rel, fromAlias) {
    if (this._aliases[path] !== undefined) {
      return this._aliases[path];
    }

    var schema = rel.to().definition();
    var source = schema.source();
    var toAlias = this.alias(path, schema);

    var table = {};
    table[source] = toAlias;

    this.statement().join(
      table,
      this._on(rel, fromAlias, toAlias),
      'LEFT'
    );
    return toAlias;
  }

  /**
   * Build the `ON` constraints from a `Relationship` instance.
   *
   * @param  object rel       A Relationship instance.
   * @param  string fromAlias The "from" model alias.
   * @param  string toAlias   The "to" model alias.
   * @return array             A constraints array.
   */
  _on(rel, fromAlias, toAlias) {
    if (rel.type() === 'hasManyThrough') {
      return [];
    }
    var keys = rel.keys();
    var fromField = Object.keys(keys)[0];
    var toField = keys[fromField];
    return { '=': [{ ':name': fromAlias + '.' + fromField },  { ':name': toAlias + '.' + toField }] };
  }

  /**
   * Return the SQL string.
   *
   * @return String
   */
  toString() {
    var statement = this._statement;
    var parts = {
      fields: statement.data('fields').slice(),
      joins: statement.data('joins').slice(),
      where: statement.data('where').slice(),
      limit: statement.data('limit')
    };
    this._applyHas();
    this._applyLimit();

    var noFields = !statement.data('fields');
    if (noFields) {
      statement.fields({ [this.alias()]: ['*'] });
    }
    var sql = statement.toString(this._schemas);

    for (var name in parts) {
      statement.data(name, parts[name]);
    }
    this._aliasCounter = {};
    this._aliases = {};
    this.alias('', this.schema());
    return sql;
  }
}

module.exports = Query;
<|endoftext|>


<commit_before>// Copyright (c) CBC/Radio-Canada. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

define(['jquery', 'jquery-deparam'],
    function($, jQueryDeparam) {
        'use strict';

        var Query = function(url, options) {
            var self = this;

            self.options = options || {};
            var xUrl = url || '';

            var urlParts = xUrl.split('?');
            //self.url = urlParts[0];

            self.__query = '';
            self.params = {};

            if (urlParts.length > 1) {
                self.__query = urlParts[1];
            }

            deparam(self);
        };

        Query.prototype.toString = function() {
            return this.__query;
        };

        Query.prototype.setParam = function(name, value) {
            var self = this;

            self.__query += (getQueryStringDelimiter(self) + name + '=' + encodeURIComponent(value));

            deparam(self);
        };

        Query.prototype.removeURLParameter = function(parameterName) {
            var self = this;

            var prefix = encodeURIComponent(parameterName) + '=';
            var pars = self.__query[1].split(/[&;]/g);

            //reverse iteration as may be destructive
            for (var i = pars.length; i-- > 0;) {
                //idiom for string.startsWith
                if (pars[i].lastIndexOf(prefix, 0) !== -1) {
                    pars.splice(i, 1);
                }
            }

            if (pars.length) {
                self.__query = '?' + pars.join('&');
            }

            self.__query = '';

            deparam(self);
        };

        function getQueryStringDelimiter(self) {
            if (self.params.length) {
                return '&';
            }

            return '?';
        }

        function deparam(self) {
            if (self.__query) {
                self.params = jQueryDeparam(self.__query, self.options.coerce);
            } else {
                self.params = {};
            }
        }

        return Query;
    });
<commit_msg>fixes..<commit_after>// Copyright (c) CBC/Radio-Canada. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

define(['jquery', 'jquery-deparam'],
    function($, jQueryDeparam) {
        'use strict';

        var Query = function(url, options) {
            var self = this;

            self.options = options || {};
            var xUrl = url || '';

            var urlParts = xUrl.split('?');
            self.__url = urlParts[0];

            self.__query = '';
            self.params = {};

            if (urlParts.length > 1) {
                self.__query = urlParts[1];
            }

            deparam(self);
        };

        Query.prototype.toString = function() {
            return this.__query;
        };

        Query.prototype.url = function() {
            return this.__url + '?' + this.__query;
        };

        Query.prototype.setParam = function(name, value) {
            var self = this;

            //todo: handle array value... for

            self.__query += (getQueryStringDelimiter(self) + name + '=' + encodeURIComponent(value));

            deparam(self);
        };

        Query.prototype.removeParam = function(paramName) {
            var self = this;

            var prefix = encodeURIComponent(paramName) + '=';
            var pars = self.__query[1].split(/[&;]/g);

            //reverse iteration as may be destructive
            for (var i = pars.length; i-- > 0;) {
                //idiom for string.startsWith
                if (pars[i].lastIndexOf(prefix, 0) !== -1) {
                    pars.splice(i, 1);
                }
            }

            if (pars.length) {
                self.__query = pars.join('&');
            }

            self.__query = '';

            deparam(self);
        };

        function getQueryStringDelimiter(self) {
            if (self.params.length) {
                return '&';
            }

            return '';
        }

        function deparam(self) {
            if (self.__query) {
                self.params = jQueryDeparam(self.__query, self.options.coerce);
            } else {
                self.params = {};
            }
        }

        return Query;
    });
<|endoftext|>


<commit_before>const Parse = require('parse/node');
const Promise = require('bluebird');
const { flatten, logger } = require('./util');

function batchQuery(where, batchSize, count, order = 'objectId') {
  const items = [];
  for (let skip = 0; skip < count; skip += batchSize) {
    if (skip > count) {
      skip = count;
    }

    items.push({
      where,
      limit: batchSize,
      skip,
      order,
    });
  }
  return items;
}

function sliceArray(array, size) {
  const results = [];
  while (array.length > 0) {
    results.push(array.slice(0, size));
    array = array.slice(size);
  }
  return results;
}

async function getObjectIds(where, querySize, previousQueryLast) {
  const installationsQ = Parse.Query.fromJSON('_Installation', {
    where,
  });
  installationsQ.exists('deviceToken');
  installationsQ.limit(querySize);
  installationsQ.select([ 'objectId' ]);
  installationsQ.ascending('objectId');
  if (previousQueryLast) {
    installationsQ.greaterThan('objectId', previousQueryLast);
  }
  const installations = await installationsQ.find({ useMasterKey: true });
  return installations.map((inst) => inst.id);
}

function smartBatch(where, batchSize, firstElement, objects = []) {
  return getObjectIds(where, batchSize, firstElement).then((results) => {
    objects.push(results);
    if (results.length === 0) {
      console.log('No results found...'); // eslint-disable-line
      return;
    }
    const last = results[results.length-1];
    console.log('Done: '+ results.length + ' ' +  last); // eslint-disable-line
    if (results.length === batchSize) {
      return smartBatch(where, batchSize, last, objects);
    }
  }).then(() => {
    if (objects.length > 0) {
      logger.info(`Creating about ${objects.length * batchSize}`);
    }
    return objects.reduce((memo, array) => {
      return memo.concat(sliceArray(array, 100));
    }, []);
  });
}

function batchPushWorkItem(pushWorkItem, batchSize = 100, querySize = 10000) {
  return smartBatch(pushWorkItem.query.where, querySize).then((slices) => {
    return slices.reduce((memo, array) => {
      return memo.concat(sliceArray(array, batchSize));
    }, []).map((slice) => {
      const batch = { objectId: { $in: slice } };
      return Object.assign({}, pushWorkItem, { query: { where: batch } });
    });
  });
}

function getScheduledPushes() {
  const pushStatusesQ = new Parse.Query('_PushStatus');
  pushStatusesQ.containedIn('status', [ 'scheduled' ]);

  return Promise.resolve(pushStatusesQ.count({ useMasterKey: true }))
    .then((count) =>
      batchQuery(pushStatusesQ.toJSON().where, 1000, count)
        .map((batch) => Parse.Query.fromJSON('_PushStatus', batch)))

    .mapSeries((query) => Promise.resolve(query.find({ useMasterKey: true }))
      .filter((pushStatus) => {
        // Filter out immediate pushes which are currently running
        if (pushStatus.get('status') === 'running' && !pushStatus.has('sentPerUTCOffset')) {
          logger.debug('Filtered out pushStatus', {
            type: 'immediate',
            pushStatus: pushStatus.toJSON(),
          });
          return false;
        }

        return true;
      }
    ))
    .then(flatten)
    .tap((pushStatuses) => {
      logger.info(`Found ${pushStatuses.length} potential pushes`, {
        pushStatuses: pushStatuses.map((p) => p.id),
      });
    });
}

function getPushesByCampaign(campaign) {
  const pushes = campaign.relation('pushes');
  return pushes.query()
    .descending('createdAt')
    .find({ useMasterKey: true });
}

function getActiveCampaigns() {
  logger.debug('Finding active campaigns');
  const campaignsQ = new Parse.Query('PushCampaign');
  return campaignsQ
    .equalTo('status', 'active')
    .include('nextPush')
    .find({ useMasterKey: true });
}

module.exports = {
  getActiveCampaigns,
  getScheduledPushes,
  getPushesByCampaign,
  batchQuery,
  batchPushWorkItem,
  smartBatch,
  getObjectIds,
};
<commit_msg>Remove unused batchPushWorkItem fn<commit_after>const Parse = require('parse/node');
const Promise = require('bluebird');
const { flatten, logger } = require('./util');

function batchQuery(where, batchSize, count, order = 'objectId') {
  const items = [];
  for (let skip = 0; skip < count; skip += batchSize) {
    if (skip > count) {
      skip = count;
    }

    items.push({
      where,
      limit: batchSize,
      skip,
      order,
    });
  }
  return items;
}

function sliceArray(array, size) {
  const results = [];
  while (array.length > 0) {
    results.push(array.slice(0, size));
    array = array.slice(size);
  }
  return results;
}

async function getObjectIds(where, querySize, previousQueryLast) {
  const installationsQ = Parse.Query.fromJSON('_Installation', {
    where,
  });
  installationsQ.exists('deviceToken');
  installationsQ.limit(querySize);
  installationsQ.select([ 'objectId' ]);
  installationsQ.ascending('objectId');
  if (previousQueryLast) {
    installationsQ.greaterThan('objectId', previousQueryLast);
  }
  const installations = await installationsQ.find({ useMasterKey: true });
  return installations.map((inst) => inst.id);
}

function smartBatch(where, batchSize, firstElement, objects = []) {
  return getObjectIds(where, batchSize, firstElement).then((results) => {
    objects.push(results);
    if (results.length === 0) {
      console.log('No results found...'); // eslint-disable-line
      return;
    }
    const last = results[results.length-1];
    console.log('Done: '+ results.length + ' ' +  last); // eslint-disable-line
    if (results.length === batchSize) {
      return smartBatch(where, batchSize, last, objects);
    }
  }).then(() => {
    if (objects.length > 0) {
      logger.info(`Creating about ${objects.length * batchSize}`);
    }
    return objects.reduce((memo, array) => {
      return memo.concat(sliceArray(array, 100));
    }, []);
  });
}

function getScheduledPushes() {
  const pushStatusesQ = new Parse.Query('_PushStatus');
  pushStatusesQ.containedIn('status', [ 'scheduled' ]);

  return Promise.resolve(pushStatusesQ.count({ useMasterKey: true }))
    .then((count) =>
      batchQuery(pushStatusesQ.toJSON().where, 1000, count)
        .map((batch) => Parse.Query.fromJSON('_PushStatus', batch)))

    .mapSeries((query) => Promise.resolve(query.find({ useMasterKey: true }))
      .filter((pushStatus) => {
        // Filter out immediate pushes which are currently running
        if (pushStatus.get('status') === 'running' && !pushStatus.has('sentPerUTCOffset')) {
          logger.debug('Filtered out pushStatus', {
            type: 'immediate',
            pushStatus: pushStatus.toJSON(),
          });
          return false;
        }

        return true;
      }
    ))
    .then(flatten)
    .tap((pushStatuses) => {
      logger.info(`Found ${pushStatuses.length} potential pushes`, {
        pushStatuses: pushStatuses.map((p) => p.id),
      });
    });
}

function getPushesByCampaign(campaign) {
  const pushes = campaign.relation('pushes');
  return pushes.query()
    .descending('createdAt')
    .find({ useMasterKey: true });
}

function getActiveCampaigns() {
  logger.debug('Finding active campaigns');
  const campaignsQ = new Parse.Query('PushCampaign');
  return campaignsQ
    .equalTo('status', 'active')
    .include('nextPush')
    .find({ useMasterKey: true });
}

module.exports = {
  getActiveCampaigns,
  getScheduledPushes,
  getPushesByCampaign,
  batchQuery,
  getObjectIds,
};
<|endoftext|>


<commit_before>const Parse = require('parse/node');
const Promise = require('bluebird');
const { flatten, logger } = require('./util');

function batchQuery(where, batchSize, count) {
  const items = [];
  for (let skip = 0; skip < count; skip += batchSize) {
    if (skip > count) {
      skip = count;
    }

    items.push({
      where,
      limit: batchSize,
      skip,
    });
  }
  return items;
}

function batchPushWorkItem(pushWorkItem, batchSize = 100) {
  const installationsQ = Parse.Query.fromJSON('_Installation', {
    where: pushWorkItem.query.where,
  });

  return installationsQ.count({ useMasterKey: true })
    .then((count) => {
      logger.info('Installations count', { count, pushWorkItem });
      return batchQuery(pushWorkItem.query.where, batchSize, count)
        .map((batch) => Object.assign({}, pushWorkItem, { query: batch }));
    });
}

function getScheduledPushes() {
  const pushStatusesQ = new Parse.Query('_PushStatus');
  pushStatusesQ.containedIn('status', [ 'scheduled' ]);

  return Promise.resolve(pushStatusesQ.count({ useMasterKey: true }))
    .then((count) =>
      batchQuery(pushStatusesQ.toJSON().where, 1000, count)
        .map((batch) => Parse.Query.fromJSON('_PushStatus', batch)))

    .mapSeries((query) => Promise.resolve(query.find({ useMasterKey: true }))
      .filter((pushStatus) => {
        // Filter out immediate pushes which are currently running
        if (pushStatus.get('status') === 'running' && !pushStatus.has('sentPerUTCOffset')) {
          logger.debug('Filtered out pushStatus', {
            type: 'immediate',
            pushStatus: pushStatus.toJSON(),
          });
          return false;
        }

        return true;
      }
    ))
    .then(flatten)
    .tap((pushStatuses) => {
      logger.info(`Found ${pushStatuses.length} potential pushes`, {
        pushStatuses: pushStatuses.map((p) => p.toJSON()),
      });
    });
}

function getPushesByCampaign(campaign) {
  const pushes = campaign.relation('pushes');
  return pushes.query()
    .descending('createdAt')
    .find({ useMasterKey: true });
}

function getActiveCampaigns() {
  logger.debug('Finding active campaigns');
  const campaignsQ = new Parse.Query('PushCampaign');
  return campaignsQ.equalTo('status', 'active')
    .find({ useMasterKey: true });
}

module.exports = {
  getActiveCampaigns,
  getScheduledPushes,
  getPushesByCampaign,
  batchQuery,
  batchPushWorkItem,
};
<commit_msg>Revert "Drop query order"<commit_after>const Parse = require('parse/node');
const Promise = require('bluebird');
const { flatten, logger } = require('./util');

function batchQuery(where, batchSize, count, order = 'createdAt') {
  const items = [];
  for (let skip = 0; skip < count; skip += batchSize) {
    if (skip > count) {
      skip = count;
    }

    items.push({
      where,
      limit: batchSize,
      skip,
      order,
    });
  }
  return items;
}

function batchPushWorkItem(pushWorkItem, batchSize = 100) {
  const installationsQ = Parse.Query.fromJSON('_Installation', {
    where: pushWorkItem.query.where,
  });

  return installationsQ.count({ useMasterKey: true })
    .then((count) => {
      logger.info('Installations count', { count, pushWorkItem });
      return batchQuery(pushWorkItem.query.where, batchSize, count)
        .map((batch) => Object.assign({}, pushWorkItem, { query: batch }));
    });
}

function getScheduledPushes() {
  const pushStatusesQ = new Parse.Query('_PushStatus');
  pushStatusesQ.containedIn('status', [ 'scheduled' ]);

  return Promise.resolve(pushStatusesQ.count({ useMasterKey: true }))
    .then((count) =>
      batchQuery(pushStatusesQ.toJSON().where, 1000, count)
        .map((batch) => Parse.Query.fromJSON('_PushStatus', batch)))

    .mapSeries((query) => Promise.resolve(query.find({ useMasterKey: true }))
      .filter((pushStatus) => {
        // Filter out immediate pushes which are currently running
        if (pushStatus.get('status') === 'running' && !pushStatus.has('sentPerUTCOffset')) {
          logger.debug('Filtered out pushStatus', {
            type: 'immediate',
            pushStatus: pushStatus.toJSON(),
          });
          return false;
        }

        return true;
      }
    ))
    .then(flatten)
    .tap((pushStatuses) => {
      logger.info(`Found ${pushStatuses.length} potential pushes`, {
        pushStatuses: pushStatuses.map((p) => p.toJSON()),
      });
    });
}

function getPushesByCampaign(campaign) {
  const pushes = campaign.relation('pushes');
  return pushes.query()
    .descending('createdAt')
    .find({ useMasterKey: true });
}

function getActiveCampaigns() {
  logger.debug('Finding active campaigns');
  const campaignsQ = new Parse.Query('PushCampaign');
  return campaignsQ.equalTo('status', 'active')
    .find({ useMasterKey: true });
}

module.exports = {
  getActiveCampaigns,
  getScheduledPushes,
  getPushesByCampaign,
  batchQuery,
  batchPushWorkItem,
};
<|endoftext|>


<commit_before>/*
QUICK.JS

Copyright 2016 Michael Krause

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

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

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

	var pages;
	var datas;
	var currentPage;

	this.go = function(args){
		main.go(args);
	};

	var protos = new function(){

		this.show = function(){
			this.style.display = "block";
		};
		this.hide = function(){
			this.style.display = "none";
		};

	};

	var nav = new function(){

		this.goto = function(to){
			pages[currentPage].hide();
			currentPage = main.pageById(to);
			pages[currentPage].show();
			history.pushState(null, null, "#"+to);
		};

	};

	var dataBind = new function(){

		/**********************************************START OBJECT.WATCH POLYFILL*************************/
		/*
		 * By Eli Grey, http://eligrey.com
		 * Public Domain.
		 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
		 */

		// object.watch
		if (!Object.prototype.watch) {
			Object.defineProperty(Object.prototype, "watch", {
				  enumerable: false
				, configurable: true
				, writable: false
				, value: function (prop, handler) {
					var
					  oldval = this[prop]
					, newval = oldval
					, getter = function () {
						return newval;
					}
					, setter = function (val) {
						oldval = newval;
						return newval = handler.call(this, prop, oldval, val);
					}
					;

					if (delete this[prop]) { // can't watch constants
						Object.defineProperty(this, prop, {
							  get: getter
							, set: setter
							, enumerable: true
							, configurable: true
						});
					}
				}
			});
		}

		// object.unwatch
		if (!Object.prototype.unwatch) {
			Object.defineProperty(Object.prototype, "unwatch", {
				  enumerable: false
				, configurable: true
				, writable: false
				, value: function (prop) {
					var val = this[prop];
					delete this[prop]; // remove accessors
					this[prop] = val;
				}
			});
		}

		/**********************************************END OBJECT.WATCH POLYFILL*************************/

		var varsBound = {};

		this.inputToBind = function(bindFrom){
			var pathToVar = bindFrom.getAttribute("qk-datato");
			var pathToParent = pathToVar.substring(0, pathToVar.lastIndexOf("."));
			var varname = pathToVar.substring(pathToVar.lastIndexOf(".")+1);
			var parent = parseDataSource(pathToParent, window);
			parent[varname] = bindFrom.value;
		};

		this.bind = function(bindTo, dataPath){
			var children = bindTo.childNodes;
			if(dataPath === undefined)
				dataPath = bindTo.getAttribute("qk-datafrom");
			for(var i = 0, l = children.length; i < l; i++){
				if(children[i].nodeName === "#text"){
					var unbound = children[i].data;
					var dataObj = parseDataSource(dataPath, window);
					var bound = getBoundString(dataObj, unbound, bindTo);
					children[i].data = bound;
				}
				else{
					this.bind(children[i], dataPath);
				}
			}
		};

		var identifyDataType = function(data){
			if(data.constructor === Array)
				return 'array';
			return 'single';
			//eventually handle more types of data
		};

		var registerVarToWatch = function(parent, value, element, unbound){
			parent.watch(String(value), function(varName, oldVal, newVal){
				varsBound[varName].e.innerHTML = varsBound[varName].u;
				setTimeout(dataBind.bind, 0, varsBound[varName].e);
				return newVal;
			});
			varsBound[String(value)] = {"e" : element, "u" : unbound};
		};

		var getBoundString = function(dataObj, unbound, element){
			var bound = "";
			var origUnbound = unbound;
			var toks = unbound.split("");

			var tokData = {"wasOpenBracket" : false, "inExp" : false, "wasCloseBracket" : false, "activePath" : ""};
			while(toks.length > 0){
				tok = toks.shift();
				if(tok === "["){
					if(tokData.wasOpenBracket){
						tokData.inExp = true;
						tokData.wasOpenBracket = false;
					}
					else{
						tokData.wasOpenBracket = true;
					}
				}
				else if(tok === "]"){
					if(tokData.wasCloseBracket){
						tokData.inExp = false;
						tokData.wasCloseBracket = false;
						var varToBind = parseDataSource(tokData.activePath.substring(tokData.activePath.indexOf("data.")+5), dataObj);
						registerVarToWatch(dataObj, tokData.activePath.substring(tokData.activePath.indexOf("data.")+5), element, origUnbound);
						bound += varToBind;
					}
					else{
						tokData.wasCloseBracket = true;
					}
				}
				else if(tokData.inExp){
					tokData.activePath += tok;
				}
				else{
					bound += tok;
				}
			}
			if(bound.indexOf("[[") > -1){
				bound = getBoundString(dataObj, bound, element);
			}
			return bound;
		};

		var parseDataSource = function(path, root){
			var loc = path.split('.');
			var obj = root;
			while(loc.length > 0){
				obj = obj[loc.shift()];
			}
			return obj;
		};
	};

	var main = new function(){

		var moduleArgs = {};

		var registerElements = function(){
			/**Define qk-page's prototype **/
			var qkpageProto = Object.create(HTMLElement.prototype);
			qkpageProto.show = protos.show;
			qkpageProto.hide = protos.hide;
			/**End qk-page's prototype **/

			/**Define qk-const's prototype **/
			var qkconstProto = Object.create(HTMLElement.prototype);
			qkconstProto.show = protos.show;
			qkconstProto.hide = protos.hide;
			/**End qk-const's prototype **/

			document.registerElement('qk-page', {
				prototype: qkpageProto
			});
			document.registerElement('qk-const', {
				prototype: qkconstProto
			});

		};

		var registerListeners = function(){
			Array.prototype.slice.call(document.querySelectorAll('[qk-linkto]')).forEach(function(current){
				current.addEventListener('click', function(){
					nav.goto(current.getAttribute("qk-linkto"));
				})
			});

			Array.prototype.slice.call(document.querySelectorAll('input[qk-datato]')).forEach(function(current){
				current.addEventListener('input', function(){
					dataBind.inputToBind(current);
				})
			});

			window.onhashchange = displayPage;
		};

		var displayPage = function(){
			if(window.location.hash === "" || window.location.hash === undefined || window.location.hash === "#"){
				var hId = currentPage = main.pageById(moduleArgs.home);
				for(var i = 0, l = pages.length; i < l; i++){
					pages[i].hide();
				}
				pages[hId].show();
				history.pushState(null, null, "#"+moduleArgs.home);
			}
			else{
				var hId = currentPage = main.pageById(window.location.hash.substring(1));
				for(var i = 0, l = pages.length; i < l; i++){
					pages[i].hide();
				}
				if(hId < 0){
					window.location.hash = "#";
				}
				else{
					pages[hId].show();
				}
			}
		};

		var fetchPagesAndData = function(){
			pages = document.querySelectorAll('qk-page[qk-pageid]');
			datas = document.querySelectorAll('[qk-datafrom]');
		};

		var triggerDataBind = function(){
			for(var i = 0, l = datas.length; i < l; i++){
				dataBind.bind(datas[i]);
			}
		};

		this.pageById = function(id){
			for(var i = 0, l = pages.length; i < l; i++){
				if(pages[i].getAttribute("qk-pageid")===id)
					return i;
			}
			return -1;
		};

		this.go = function(args){
			registerElements();
			registerListeners();
			fetchPagesAndData();
			triggerDataBind();
			moduleArgs = args;
			displayPage();
		};
	};

};<commit_msg>started array support<commit_after>/*
QUICK.JS

Copyright 2016 Michael Krause

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

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

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

	var pages;
	var datas;
	var currentPage;

	this.go = function(args){
		main.go(args);
	};

	var protos = new function(){

		this.show = function(){
			this.style.display = "block";
		};
		this.hide = function(){
			this.style.display = "none";
		};

	};

	var nav = new function(){

		this.goto = function(to){
			pages[currentPage].hide();
			currentPage = main.pageById(to);
			pages[currentPage].show();
			history.pushState(null, null, "#"+to);
		};

	};

	var dataBind = new function(){

		/**********************************************START OBJECT.WATCH POLYFILL*************************/
		/*
		 * By Eli Grey, http://eligrey.com
		 * Public Domain.
		 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
		 */

		// object.watch
		if (!Object.prototype.watch) {
			Object.defineProperty(Object.prototype, "watch", {
				  enumerable: false
				, configurable: true
				, writable: false
				, value: function (prop, handler) {
					var
					  oldval = this[prop]
					, newval = oldval
					, getter = function () {
						return newval;
					}
					, setter = function (val) {
						oldval = newval;
						return newval = handler.call(this, prop, oldval, val);
					}
					;

					if (delete this[prop]) { // can't watch constants
						Object.defineProperty(this, prop, {
							  get: getter
							, set: setter
							, enumerable: true
							, configurable: true
						});
					}
				}
			});
		}

		// object.unwatch
		if (!Object.prototype.unwatch) {
			Object.defineProperty(Object.prototype, "unwatch", {
				  enumerable: false
				, configurable: true
				, writable: false
				, value: function (prop) {
					var val = this[prop];
					delete this[prop]; // remove accessors
					this[prop] = val;
				}
			});
		}

		/**********************************************END OBJECT.WATCH POLYFILL*************************/

		var varsBound = {};

		this.inputToBind = function(bindFrom){
			var pathToVar = bindFrom.getAttribute("qk-datato");
			var pathToParent = pathToVar.substring(0, pathToVar.lastIndexOf("."));
			var varname = pathToVar.substring(pathToVar.lastIndexOf(".")+1);
			var parent = parseDataSource(pathToParent, window);
			parent[varname] = bindFrom.value;
		};

		this.bind = function(bindTo, dataPath){
			var children = bindTo.childNodes;
			if(dataPath === undefined)
				dataPath = bindTo.getAttribute("qk-datafrom");
			for(var i = 0, l = children.length; i < l; i++){
				if(children[i].nodeName === "#text"){
					var unbound = children[i].data;
					var dataObj = parseDataSource(dataPath, window);
					var bound = getBoundString(dataObj, unbound, bindTo);
					children[i].data = bound;
				}
				else{
					this.bind(children[i], dataPath);
				}
			}
		};

		/*var identifyDataType = function(data){
			if(data.constructor === Array)
				return 'array';
			return 'single';
			//eventually handle more types of data
		};*/

		var registerVarToWatch = function(parent, value, element, unbound){
			parent.watch(String(value), function(varName, oldVal, newVal){
				varsBound[varName].e.innerHTML = varsBound[varName].u;
				setTimeout(dataBind.bind, 0, varsBound[varName].e);
				return newVal;
			});
			varsBound[String(value)] = {"e" : element, "u" : unbound};
		};

		var getBoundString = function(dataObj, unbound, element){
			var bound = "";
			var origUnbound = unbound;
			var toks = unbound.split("");

			var tokData = {"wasOpenBracket" : false, "inExp" : false, "wasCloseBracket" : false, "activePath" : ""};
			while(toks.length > 0){
				tok = toks.shift();
				if(tok === "["){
					if(tokData.wasOpenBracket){
						tokData.inExp = true;
						tokData.wasOpenBracket = false;
					}
					else{
						tokData.wasOpenBracket = true;
					}
				}
				else if(tok === "]"){
					if(tokData.wasCloseBracket){
						tokData.inExp = false;
						tokData.wasCloseBracket = false;
						var varToBind = parseDataSource(tokData.activePath.substring(tokData.activePath.indexOf("data.")+5), dataObj);
						registerVarToWatch(dataObj, tokData.activePath.substring(tokData.activePath.indexOf("data.")+5), element, origUnbound);
						bound += varToBind.toString();
					}
					else{
						tokData.wasCloseBracket = true;
					}
				}
				else if(tokData.inExp){
					tokData.activePath += tok;
				}
				else{
					bound += tok;
				}
			}
			if(bound.indexOf("[[") > -1){
				bound = getBoundString(dataObj, bound, element);
			}
			return bound;
		};

		var parseDataSource = function(path, root){
			var loc = path.split('.');
			var obj = root;
			while(loc.length > 0){
				obj = obj[loc.shift()];
			}
			return obj;
		};
	};

	var main = new function(){

		var moduleArgs = {};

		var registerElements = function(){
			/**Define qk-page's prototype **/
			var qkpageProto = Object.create(HTMLElement.prototype);
			qkpageProto.show = protos.show;
			qkpageProto.hide = protos.hide;
			/**End qk-page's prototype **/

			/**Define qk-const's prototype **/
			var qkconstProto = Object.create(HTMLElement.prototype);
			qkconstProto.show = protos.show;
			qkconstProto.hide = protos.hide;
			/**End qk-const's prototype **/

			document.registerElement('qk-page', {
				prototype: qkpageProto
			});
			document.registerElement('qk-const', {
				prototype: qkconstProto
			});

		};

		var registerListeners = function(){
			Array.prototype.slice.call(document.querySelectorAll('[qk-linkto]')).forEach(function(current){
				current.addEventListener('click', function(){
					nav.goto(current.getAttribute("qk-linkto"));
				})
			});

			Array.prototype.slice.call(document.querySelectorAll('input[qk-datato]')).forEach(function(current){
				current.addEventListener('input', function(){
					dataBind.inputToBind(current);
				})
			});

			window.onhashchange = displayPage;
		};

		var displayPage = function(){
			if(window.location.hash === "" || window.location.hash === undefined || window.location.hash === "#"){
				var hId = currentPage = main.pageById(moduleArgs.home);
				for(var i = 0, l = pages.length; i < l; i++){
					pages[i].hide();
				}
				pages[hId].show();
				history.pushState(null, null, "#"+moduleArgs.home);
			}
			else{
				var hId = currentPage = main.pageById(window.location.hash.substring(1));
				for(var i = 0, l = pages.length; i < l; i++){
					pages[i].hide();
				}
				if(hId < 0){
					window.location.hash = "#";
				}
				else{
					pages[hId].show();
				}
			}
		};

		var fetchPagesAndData = function(){
			pages = document.querySelectorAll('qk-page[qk-pageid]');
			datas = document.querySelectorAll('[qk-datafrom]');
		};

		var triggerDataBind = function(){
			for(var i = 0, l = datas.length; i < l; i++){
				dataBind.bind(datas[i]);
			}
		};

		this.pageById = function(id){
			for(var i = 0, l = pages.length; i < l; i++){
				if(pages[i].getAttribute("qk-pageid")===id)
					return i;
			}
			return -1;
		};

		this.go = function(args){
			registerElements();
			registerListeners();
			fetchPagesAndData();
			triggerDataBind();
			moduleArgs = args;
			displayPage();
		};
	};

};<|endoftext|>


<commit_before>import { makeStr } from './helper/makeStr'
import { surround } from './surround'

export let quote = (str, quoteChar = '"') => {
  return surround(str, quoteChar)
}
<commit_msg>delete unnessary import<commit_after>import { surround } from './surround'

export let quote = (str, quoteChar = '"') => {
  return surround(str, quoteChar)
}
<|endoftext|>