<commit_before>export default class PlayerController {
	constructor($rootScope, $scope, $log, $timeout, $uibModal, _, toastr, playersApi, playerModel, websocket) {
		'ngInject';

		this.$rootScope = $rootScope;
		this.$scope = $scope;
		this.$log = $log.getInstance(this.constructor.name);
		this.$timeout = $timeout;
		this.$uibModal = $uibModal;

		this._ = _;
		this.toastr = toastr;

		this.playerModel = playerModel; // Used in template
		this.playersApi = playersApi;
		this.ws = websocket;

		this.$log.debug('constructor()', this);
	}

	$onInit() {
		this.$log.debug('$onInit()', this);

		this.$rootScope.$on('deck:action:winter', () => {
			this.$log.debug('deck:action:winter');

			if (this.player.id === this.playerModel.model.player.id) {
				this.playerModel.showSpecialCards();
			}
		});
	}

	$onDestroy() {
		this.$log.debug('$onDestroy()', this);
	}

	canStoreCards(cardsSelected) {
		if (this.game.actionCard || !cardsSelected || cardsSelected.length !== 3) {
			return false;
		}

		let cardsMatch = this._.reduce(cardsSelected, (prev, current, index, array) => {
			let len = array.length,
				sameCard = prev.amount === current.amount;

			if (!prev) {
				// If the previous object is 'false', then cards don't match
				return false;
			} else if (sameCard && index === len - 1) {
				// Reached the end of the array and all values matched
				return true;
			}

			return sameCard ? current : false;
		});

		return cardsMatch;
	}

	getCurrentPlayer() {
		return this.playerModel.model.player;
	}

	onStorageClick(evt) {
		let cardsSelected = this.player.cardsSelected;

		this.$log.debug('onStorageClick()', evt, cardsSelected, this);

		evt.preventDefault();

		if (this.player.isActive && this.player.hasDrawnCard && this.canStoreCards(cardsSelected)) {
			this.storeCards(cardsSelected);
		} else {
			this.showStorage(this.player);
		}
	}

	onStorageAutoClick(evt) {
		evt.preventDefault();

		if (this.player.isActive && !this.player.isFirstTurn) {
			// Filter out sets of 3 that have the same 'amount'
			this.playerModel.getCards()
				.then(res => {
					let cards = this._.filter(res.data, (o) => {
							return o.cardType === 'number';
						}),
						numberCards = this._.groupBy(cards, (o) => {
							return o.amount;
						}),
						isStored = false,
						timeout = 0;

					this.$log.debug('numberCards -> ', numberCards);

					this._.forEach(numberCards, (cardsGroup) => {
						let numGroups = Math.floor(cardsGroup.length / 3),
							numToStore = numGroups * 3;

						if (numToStore) {
							let cardsToStore = this._.sampleSize(cardsGroup, numToStore);
							this.$log.debug('cardsToStore -> ', cardsToStore);

							this.$timeout(() => {
								this.storeCards(cardsToStore);
							}, timeout);

							timeout += 250;
							isStored = true;
						}
					});

					if (!isStored) {
						this.toastr.warning('No matching cards to store!');
					}
				}, (err) => {
					this.$log.error(err);
				});
		}
	}

	storeCards(cardsSelected) {
		let cardsInStorage = this.player.cardsInStorage,
			cardsInHand = this.player.cardsInHand,
			onSuccess = (res => {
				if (res.status == 200) {
					this.$log.debug(res);
				}
			}),
			onError = (err => {
				this.$log.error(err);
			}),
			cardIds = this._.map(cardsSelected, (obj) => {
				return obj.id;
			});

		cardsInStorage.push(cardIds[0]);
		this._.pullAll(cardsInHand, cardIds);

		let plData = {
			cardsInHand: cardsInHand,
			cardsInStorage: cardsInStorage,
			isFirstTurn: false,
			score: this.player.score + cardsSelected[0].amount
		};

		this.playersApi
			.update(this.player.id, plData)
			.then(onSuccess, onError);

		this.player.cardsSelected = [];
	}

	showStorage(pl) {
		let onClose = (data => {
				this.$log.debug('onClose()', data, this);
			}),
			onError = (err => {
				if (err !== 'backdrop click') {
					this.$log.error(err);
				}
			});

		this.$log.debug('showStorage()', pl, this);

		let modal = this.$uibModal.open({
			appendTo: angular.element(document).find('players'),
			component: 'storageModal',
			resolve: {
				player: () => {
					return this.player;
				}
			}
		});

		modal.result.then(onClose, onError);
	}
}
<commit_msg>Add isCurrentPlayer() method to PlayerController to fix bug<commit_after>export default class PlayerController {
	constructor($rootScope, $scope, $log, $timeout, $uibModal, _, toastr, playersApi, playerModel, websocket) {
		'ngInject';

		this.$rootScope = $rootScope;
		this.$scope = $scope;
		this.$log = $log.getInstance(this.constructor.name);
		this.$timeout = $timeout;
		this.$uibModal = $uibModal;

		this._ = _;
		this.toastr = toastr;

		this.playerModel = playerModel; // Used in template
		this.playersApi = playersApi;
		this.ws = websocket;

		this.$log.debug('constructor()', this);
	}

	$onInit() {
		this.$log.debug('$onInit()', this);

		this.$rootScope.$on('deck:action:winter', () => {
			this.$log.debug('deck:action:winter');

			if (this.player.id === this.playerModel.model.player.id) {
				this.playerModel.showSpecialCards();
			}
		});
	}

	$onDestroy() {
		this.$log.debug('$onDestroy()', this);
	}

	canStoreCards(cardsSelected) {
		if (this.game.actionCard || !cardsSelected || cardsSelected.length !== 3) {
			return false;
		}

		let cardsMatch = this._.reduce(cardsSelected, (prev, current, index, array) => {
			let len = array.length,
				sameCard = prev.amount === current.amount;

			if (!prev) {
				// If the previous object is 'false', then cards don't match
				return false;
			} else if (sameCard && index === len - 1) {
				// Reached the end of the array and all values matched
				return true;
			}

			return sameCard ? current : false;
		});

		return cardsMatch;
	}

	getCurrentPlayer() {
		return this.playerModel.getByProp();
	}

	isCurrentPlayer() {
		return this.player.isCurrent;
	}

	onStorageClick(evt) {
		let cardsSelected = this.player.cardsSelected;

		this.$log.debug('onStorageClick()', evt, cardsSelected, this);

		evt.preventDefault();

		if (this.player.isActive && this.player.hasDrawnCard && this.canStoreCards(cardsSelected)) {
			this.storeCards(cardsSelected);
		} else {
			this.showStorage(this.player);
		}
	}

	onStorageAutoClick(evt) {
		evt.preventDefault();

		if (this.player.isActive && !this.player.isFirstTurn) {
			// Filter out sets of 3 that have the same 'amount'
			this.playerModel.getCards()
				.then(res => {
					let cards = this._.filter(res.data, (o) => {
							return o.cardType === 'number';
						}),
						numberCards = this._.groupBy(cards, (o) => {
							return o.amount;
						}),
						isStored = false,
						timeout = 0;

					this.$log.debug('numberCards -> ', numberCards);

					this._.forEach(numberCards, (cardsGroup) => {
						let numGroups = Math.floor(cardsGroup.length / 3),
							numToStore = numGroups * 3;

						if (numToStore) {
							let cardsToStore = this._.sampleSize(cardsGroup, numToStore);
							this.$log.debug('cardsToStore -> ', cardsToStore);

							this.$timeout(() => {
								this.storeCards(cardsToStore);
							}, timeout);

							timeout += 250;
							isStored = true;
						}
					});

					if (!isStored) {
						this.toastr.warning('No matching cards to store!');
					}
				}, (err) => {
					this.$log.error(err);
				});
		}
	}

	storeCards(cardsSelected) {
		let cardsInStorage = this.player.cardsInStorage,
			cardsInHand = this.player.cardsInHand,
			onSuccess = (res => {
				if (res.status == 200) {
					this.$log.debug(res);
				}
			}),
			onError = (err => {
				this.$log.error(err);
			}),
			cardIds = this._.map(cardsSelected, (obj) => {
				return obj.id;
			});

		cardsInStorage.push(cardIds[0]);
		this._.pullAll(cardsInHand, cardIds);

		let plData = {
			cardsInHand: cardsInHand,
			cardsInStorage: cardsInStorage,
			isFirstTurn: false,
			score: this.player.score + cardsSelected[0].amount
		};

		this.playersApi
			.update(this.player.id, plData)
			.then(onSuccess, onError);

		this.player.cardsSelected = [];
	}

	showStorage(pl) {
		let onClose = (data => {
				this.$log.debug('onClose()', data, this);
			}),
			onError = (err => {
				if (err !== 'backdrop click') {
					this.$log.error(err);
				}
			});

		this.$log.debug('showStorage()', pl, this);

		let modal = this.$uibModal.open({
			appendTo: angular.element(document).find('players'),
			component: 'storageModal',
			resolve: {
				player: () => {
					return this.player;
				}
			}
		});

		modal.result.then(onClose, onError);
	}
}
<|endoftext|>


<commit_before>const semver = require('semver')
const autoUpdater = require('electron').autoUpdater
const got = require('got')
const events = require('events')

const WIN32 = (process.platform === 'win32')
const DARWIN = (process.platform === 'darwin')
const REGEX_ZIP_URL = /\/v(\d+\.\d+\.\d+)\/.*\.zip/

export default class GhReleases extends events.EventEmitter {

  constructor (gh) {
    super()

    let self = this

    self.repo = gh.repo
    self.repoUrl = 'https://github.com/' + gh.repo
    self.currentVersion = gh.currentVersion
    self.autoUpdater = autoUpdater

    self.autoUpdater.on('update-downloaded', (...args) => self.emit('update-downloaded', args))
  }

  /**
   * Get tags from this.repo
   */
  _getLatestTag () {
    let url = this.repoUrl + '/releases/latest'
    return got.head(url)
      .then(res => {
        let latestTag = res.socket._httpMessage.path.split('/').pop()
        return latestTag
      })
      .catch(err => {
        if (err) throw new Error('Unable to get latest release tag from Github.')
      })
  }

  /**
   * Get current version from app.
   */
  _getCurrentVersion () {
    return this.currentVersion
  }

  /**
   * Compare current with the latest version.
   */
  _newVersion (latest) {
    return semver.lt(this._getCurrentVersion(), latest)
  }

  /**
   * Get the feed URL from this.repo
   */
  _getFeedUrl (tag) {
    let feedUrl

    // If on Windows
    if (WIN32) {
      return new Promise((resolve, reject) => {
        feedUrl = this.repoUrl + '/releases/download/' + tag
        resolve(feedUrl)
      })
    }

    // On Mac we need to use the `auto_updater.json`
    feedUrl = 'https://raw.githubusercontent.com/' + this.repo + '/master/auto_updater.json'

    // Make sure feedUrl exists
    return got.get(feedUrl)
      .then(res => {
        if (res.statusCode === 404) {
          throw new Error('auto_updater.json does not exist.')
        } else if (res.statusCode !== 200) {
          throw new Error('Unable to fetch auto_updater.json: ' + res.body)
        }

        let zipUrl
        try {
          zipUrl = JSON.parse(res.body).url
        } catch (err) {
          throw new Error('Unable to parse the auto_updater.json: ' + err.message + ', body: ' + res.body)
        }

        const matchReleaseUrl = zipUrl.match(REGEX_ZIP_URL)
        if (!matchReleaseUrl) {
          throw new Error('The zipUrl (' + zipUrl + ') is a invalid release URL')
        }

        const versionInZipUrl = matchReleaseUrl[1]
        const latestVersion = semver.clean(tag)
        if (versionInZipUrl !== latestVersion) {
          throw new Error('The feedUrl does not link to latest tag (zipUrl=' + versionInZipUrl + '; latestVersion=' + latestVersion + ')')
        }

        return feedUrl
      })
  }

  /**
   * Check for updates.
   */
  check (cb) {
    if (!DARWIN && !WIN32) return cb(new Error('This platform is not supported.'), false)

    let self = this

    // Get latest released version from Github.
    this._getLatestTag()
      .then(tag => {
        // Check if tag is valid semver
        if (!tag || !semver.valid(semver.clean(tag))) {
          throw new Error('Could not find a valid release tag.')
        }

        // Compare with current version.
        if (!self._newVersion(tag)) {
          throw new Error('There is no newer version.')
        }

        // There is a new version!
        // Get feed url from gh repo.
        return self._getFeedUrl(tag)
      })
      .then(feedUrl => {
        // Set feedUrl in auto_updater.
        this.autoUpdater.setFeedURL(feedUrl)

        cb(null, true)
      })
      .catch(err => {
        cb(err || null, false)
      })
  }

  /**
   * Download latest release.
   */
  download () {
    // Run auto_updater
    // Lets do this. :o
    this.autoUpdater.checkForUpdates()
  }

  /**
   * Install the downloaded update
   */
  install () {
    // Run autoUpdaters quitAndInstall()
    // This will restart the app and install the new update.
    this.autoUpdater.quitAndInstall()
  }
}
<commit_msg>PR: Allow "v" in tag name to be optional<commit_after>const semver = require('semver')
const autoUpdater = require('electron').autoUpdater
const got = require('got')
const events = require('events')

const WIN32 = (process.platform === 'win32')
const DARWIN = (process.platform === 'darwin')
const REGEX_ZIP_URL = /\/(v)?(\d+\.\d+\.\d+)\/.*\.zip/

export default class GhReleases extends events.EventEmitter {

  constructor (gh) {
    super()

    let self = this

    self.repo = gh.repo
    self.repoUrl = 'https://github.com/' + gh.repo
    self.currentVersion = gh.currentVersion
    self.autoUpdater = autoUpdater

    self.autoUpdater.on('update-downloaded', (...args) => self.emit('update-downloaded', args))
  }

  /**
   * Get tags from this.repo
   */
  _getLatestTag () {
    let url = this.repoUrl + '/releases/latest'
    return got.head(url)
      .then(res => {
        let latestTag = res.socket._httpMessage.path.split('/').pop()
        return latestTag
      })
      .catch(err => {
        if (err) throw new Error('Unable to get latest release tag from Github.')
      })
  }

  /**
   * Get current version from app.
   */
  _getCurrentVersion () {
    return this.currentVersion
  }

  /**
   * Compare current with the latest version.
   */
  _newVersion (latest) {
    return semver.lt(this._getCurrentVersion(), latest)
  }

  /**
   * Get the feed URL from this.repo
   */
  _getFeedUrl (tag) {
    let feedUrl

    // If on Windows
    if (WIN32) {
      return new Promise((resolve, reject) => {
        feedUrl = this.repoUrl + '/releases/download/' + tag
        resolve(feedUrl)
      })
    }

    // On Mac we need to use the `auto_updater.json`
    feedUrl = 'https://raw.githubusercontent.com/' + this.repo + '/master/auto_updater.json'

    // Make sure feedUrl exists
    return got.get(feedUrl)
      .then(res => {
        if (res.statusCode === 404) {
          throw new Error('auto_updater.json does not exist.')
        } else if (res.statusCode !== 200) {
          throw new Error('Unable to fetch auto_updater.json: ' + res.body)
        }

        let zipUrl
        try {
          zipUrl = JSON.parse(res.body).url
        } catch (err) {
          throw new Error('Unable to parse the auto_updater.json: ' + err.message + ', body: ' + res.body)
        }

        const matchReleaseUrl = zipUrl.match(REGEX_ZIP_URL)
        if (!matchReleaseUrl) {
          throw new Error('The zipUrl (' + zipUrl + ') is a invalid release URL')
        }

        const versionInZipUrl = matchReleaseUrl[matchReleaseUrl.length -1]
        const latestVersion = semver.clean(tag)
        if (versionInZipUrl !== latestVersion) {
          throw new Error('The feedUrl does not link to latest tag (zipUrl=' + versionInZipUrl + '; latestVersion=' + latestVersion + ')')
        }

        return feedUrl
      })
  }

  /**
   * Check for updates.
   */
  check (cb) {
    if (!DARWIN && !WIN32) return cb(new Error('This platform is not supported.'), false)

    let self = this

    // Get latest released version from Github.
    this._getLatestTag()
      .then(tag => {
        // Check if tag is valid semver
        if (!tag || !semver.valid(semver.clean(tag))) {
          throw new Error('Could not find a valid release tag.')
        }

        // Compare with current version.
        if (!self._newVersion(tag)) {
          throw new Error('There is no newer version.')
        }

        // There is a new version!
        // Get feed url from gh repo.
        return self._getFeedUrl(tag)
      })
      .then(feedUrl => {
        // Set feedUrl in auto_updater.
        this.autoUpdater.setFeedURL(feedUrl)

        cb(null, true)
      })
      .catch(err => {
        cb(err || null, false)
      })
  }

  /**
   * Download latest release.
   */
  download () {
    // Run auto_updater
    // Lets do this. :o
    this.autoUpdater.checkForUpdates()
  }

  /**
   * Install the downloaded update
   */
  install () {
    // Run autoUpdaters quitAndInstall()
    // This will restart the app and install the new update.
    this.autoUpdater.quitAndInstall()
  }
}
<|endoftext|>


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

'use strict';

(function(scope) {
  if (scope['Proxy']) {
    return;
  }

  console.log("Added proxy-polyfill")
  let lastRevokeFn = null;

  /**
   * @param {*} o
   * @return {boolean} whether this is probably a (non-null) Object
   */
  function isObject(o) {
    return o ? (typeof o == 'object' || typeof o == 'function') : false;
  }

  /**
   * @constructor
   * @param {!Object} target
   * @param {{apply, construct, get, set}} handler
   */
  scope.Proxy = function(target, handler) {
    if (!isObject(target) || !isObject(handler)) {
      throw new TypeError('Cannot create proxy with a non-object as target or handler');
    }

    // Construct revoke function, and set lastRevokeFn so that Proxy.revocable can steal it.
    // The caller might get the wrong revoke function if a user replaces or wraps scope.Proxy
    // to call itself, but that seems unlikely especially when using the polyfill.
    let throwRevoked = function() {};
    lastRevokeFn = function() {
      throwRevoked = function(trap) {
        throw new TypeError(`Cannot perform '${trap}' on a proxy that has been revoked`);
      };
    };

    // Fail on unsupported traps: Chrome doesn't do this, but ensure that users of the polyfill
    // are a bit more careful. Copy the internal parts of handler to prevent user changes.
    const unsafeHandler = handler;
    handler = {'get': null, 'set': null, 'apply': null, 'construct': null};
    for (let k in unsafeHandler) {
      if (!(k in handler)) {
        throw new TypeError(`Proxy polyfill does not support trap '${k}'`);
      }
      handler[k] = unsafeHandler[k];
    }
    if (typeof unsafeHandler == 'function') {
      // Allow handler to be a function (which has an 'apply' method). This matches what is
      // probably a bug in native versions. It treats the apply call as a trap to be configured.
      handler.apply = unsafeHandler.apply.bind(unsafeHandler);
    }

    // Define proxy as this, or a Function (if either it's callable, or apply is set).
    // TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it.
    let proxy = this;
    let isMethod = false;
    const targetIsFunction = typeof target == 'function';
    if (handler.apply || handler['construct'] || targetIsFunction) {
      proxy = function Proxy() {
        const usingNew = (this && this.constructor === proxy);
        const args = Array.prototype.slice.call(arguments);
        throwRevoked(usingNew ? 'construct' : 'apply');

        if (usingNew && handler['construct']) {
          return handler['construct'].call(this, target, args);
        } else if (!usingNew && handler.apply) {
          return handler.apply(target, this, args);
        } else if (targetIsFunction) {
          // since the target was a function, fallback to calling it directly.
          if (usingNew) {
            // inspired by answers to https://stackoverflow.com/q/1606797
            args.unshift(target);  // pass class as first arg to constructor, although irrelevant
            // nb. cast to convince Closure compiler that this is a constructor
            const f = /** @type {!Function} */ (target.bind.apply(target, args));
            return new f();
          }
          return target.apply(this, args);
        }
        throw new TypeError(usingNew ? 'not a constructor' : 'not a function');
      };
      isMethod = true;
    }

    // Create default getters/setters. Create different code paths as handler.get/handler.set can't
    // change after creation.
    const getter = handler.get ? function(prop) {
      throwRevoked('get');
      return handler.get(this, prop, proxy);
    } : function(prop) {
      throwRevoked('get');
      return this[prop];
    };
    const setter = handler.set ? function(prop, value) {
      throwRevoked('set');
      const status = handler.set(this, prop, value, proxy);
      if (!status) {
        // TODO(samthor): If the calling code is in strict mode, throw TypeError.
        // It's (sometimes) possible to work this out, if this code isn't strict- try to load the
        // callee, and if it's available, that code is non-strict. However, this isn't exhaustive.
      }
    } : function(prop, value) {
      throwRevoked('set');
      this[prop] = value;
    };

    // Clone direct properties (i.e., not part of a prototype).
    const propertyNames = Object.getOwnPropertyNames(target);
    const propertyMap = {};
    propertyNames.forEach(function(prop) {
      if (isMethod && prop in proxy) {
        return;  // ignore properties already here, e.g. 'bind', 'prototype' etc
      }
      const real = Object.getOwnPropertyDescriptor(target, prop);
      const desc = {
        enumerable: !!real.enumerable,
        get: getter.bind(target, prop),
        set: setter.bind(target, prop),
      };
      Object.defineProperty(proxy, prop, desc);
      propertyMap[prop] = true;
    });

    // Set the prototype, or clone all prototype methods (always required if a getter is provided).
    // TODO(samthor): We don't allow prototype methods to be set. It's (even more) awkward.
    // An alternative here would be to _just_ clone methods to keep behavior consistent.
    let prototypeOk = true;
    if (Object.setPrototypeOf) {
      Object.setPrototypeOf(proxy, Object.getPrototypeOf(target));
    } else if (proxy.__proto__) {
      proxy.__proto__ = target.__proto__;
    } else {
      prototypeOk = false;
    }
    if (handler.get || !prototypeOk) {
      for (let k in target) {
        if (propertyMap[k]) {
          continue;
        }
        Object.defineProperty(proxy, k, {get: getter.bind(target, k)});
      }
    }

    // The Proxy polyfill cannot handle adding new properties. Seal the target and proxy.
    Object.seal(target);
    Object.seal(proxy);

    return proxy;  // nb. if isMethod is true, proxy != this
  };

  scope.Proxy.revocable = function(target, handler) {
    const p = new scope.Proxy(target, handler);
    return {'proxy': p, 'revoke': lastRevokeFn};
  };

  scope.Proxy['revocable'] = scope.Proxy.revocable;
  scope['Proxy'] = scope.Proxy;
})(typeof process !== 'undefined' && {}.toString.call(process) == '[object process]' ? global : self);
<commit_msg>Fixed proxy polyfill to work with IE10<commit_after>/*
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

'use strict';

(function(scope) {
  if (scope['Proxy']) {
    return;
  }

  console.log("Added proxy-polyfill");
  var lastRevokeFn = null;

  /**
   * @param {*} o
   * @return {boolean} whether this is probably a (non-null) Object
   */
  function isObject(o) {
    return o ? (typeof o == 'object' || typeof o == 'function') : false;
  }

  /**
   * @constructor
   * @param {!Object} target
   * @param {{apply, construct, get, set}} handler
   */
  scope.Proxy = function(target, handler) {
    if (!isObject(target) || !isObject(handler)) {
      throw new TypeError('Cannot create proxy with a non-object as target or handler');
    }

    // Construct revoke function, and set lastRevokeFn so that Proxy.revocable can steal it.
    // The caller might get the wrong revoke function if a user replaces or wraps scope.Proxy
    // to call itself, but that seems unlikely especially when using the polyfill.
    var throwRevoked = function() {};
    lastRevokeFn = function() {
      throwRevoked = function(trap) {
        throw new TypeError("Cannot perform '" + trap + "' on a proxy that has been revoked");
      };
    };

    // Fail on unsupported traps: Chrome doesn't do this, but ensure that users of the polyfill
    // are a bit more careful. Copy the internal parts of handler to prevent user changes.
    var unsafeHandler = handler;
    handler = {'get': null, 'set': null, 'apply': null, 'construct': null};
    for (var k in unsafeHandler) {
      if (!(k in handler)) {
        throw new TypeError("Proxy polyfill does not support trap '" + k + "'");
      }
      handler[k] = unsafeHandler[k];
    }
    if (typeof unsafeHandler == 'function') {
      // Allow handler to be a function (which has an 'apply' method). This matches what is
      // probably a bug in native versions. It treats the apply call as a trap to be configured.
      handler.apply = unsafeHandler.apply.bind(unsafeHandler);
    }

    // Define proxy as this, or a Function (if either it's callable, or apply is set).
    // TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it.
    var proxy = this;
    var isMethod = false;
    var targetIsFunction = typeof target == 'function';
    if (handler.apply || handler['construct'] || targetIsFunction) {
      proxy = function Proxy() {
        var usingNew = (this && this.constructor === proxy);
        var args = Array.prototype.slice.call(arguments);
        throwRevoked(usingNew ? 'construct' : 'apply');

        if (usingNew && handler['construct']) {
          return handler['construct'].call(this, target, args);
        } else if (!usingNew && handler.apply) {
          return handler.apply(target, this, args);
        } else if (targetIsFunction) {
          // since the target was a function, fallback to calling it directly.
          if (usingNew) {
            // inspired by answers to https://stackoverflow.com/q/1606797
            args.unshift(target);  // pass class as first arg to constructor, although irrelevant
            // nb. cast to convince Closure compiler that this is a constructor
            var f = /** @type {!Function} */ (target.bind.apply(target, args));
            return new f();
          }
          return target.apply(this, args);
        }
        throw new TypeError(usingNew ? 'not a constructor' : 'not a function');
      };
      isMethod = true;
    }

    // Create default getters/setters. Create different code paths as handler.get/handler.set can't
    // change after creation.
    var getter = handler.get ? function(prop) {
      throwRevoked('get');
      return handler.get(this, prop, proxy);
    } : function(prop) {
      throwRevoked('get');
      return this[prop];
    };
    var setter = handler.set ? function(prop, value) {
      throwRevoked('set');
      var status = handler.set(this, prop, value, proxy);
      if (!status) {
        // TODO(samthor): If the calling code is in strict mode, throw TypeError.
        // It's (sometimes) possible to work this out, if this code isn't strict- try to load the
        // callee, and if it's available, that code is non-strict. However, this isn't exhaustive.
      }
    } : function(prop, value) {
      throwRevoked('set');
      this[prop] = value;
    };

    // Clone direct properties (i.e., not part of a prototype).
    var propertyNames = Object.getOwnPropertyNames(target);
    var propertyMap = {};
    propertyNames.forEach(function(prop) {
      if (isMethod && prop in proxy) {
        return;  // ignore properties already here, e.g. 'bind', 'prototype' etc
      }
      var real = Object.getOwnPropertyDescriptor(target, prop);
      var desc = {
        enumerable: !!real.enumerable,
        get: getter.bind(target, prop),
        set: setter.bind(target, prop),
      };
      Object.defineProperty(proxy, prop, desc);
      propertyMap[prop] = true;
    });

    // Set the prototype, or clone all prototype methods (always required if a getter is provided).
    // TODO(samthor): We don't allow prototype methods to be set. It's (even more) awkward.
    // An alternative here would be to _just_ clone methods to keep behavior consistent.
    var prototypeOk = true;
    if (Object.setPrototypeOf) {
      Object.setPrototypeOf(proxy, Object.getPrototypeOf(target));
    } else if (proxy.__proto__) {
      proxy.__proto__ = target.__proto__;
    } else {
      prototypeOk = false;
    }
    if (handler.get || !prototypeOk) {
      for (var k in target) {
        if (propertyMap[k]) {
          continue;
        }
        Object.defineProperty(proxy, k, {get: getter.bind(target, k)});
      }
    }

    // The Proxy polyfill cannot handle adding new properties. Seal the target and proxy.
    Object.seal(target);
    Object.seal(proxy);

    return proxy;  // nb. if isMethod is true, proxy != this
  };

  scope.Proxy.revocable = function(target, handler) {
    var p = new scope.Proxy(target, handler);
    return {'proxy': p, 'revoke': lastRevokeFn};
  };

  scope.Proxy['revocable'] = scope.Proxy.revocable;
  scope['Proxy'] = scope.Proxy;
})(typeof process !== 'undefined' && {}.toString.call(process) == '[object process]' ? global : self);
<|endoftext|>


<commit_before>// Find all AP's with ACTIVE or TRAINING status.  If not in aps group add,
// if not longer active remove.
var db = db.getSiblingDB( 'NHWG' );
var baseGroupName = 'aps';
var googleGroup = baseGroupName + '@nhwg.cap.gov';
var groupsCollection = 'GoogleGroups';
var Achvs = db.getCollection("MbrAchievements");
var achvIds = [ 193, ];
// This a left join
var activePipeline =  [
        {
            "$match" : {
                "AchvID" : { "$in": achvIds },
             }
        },
        {
            "$lookup" : {
                "from" : "Google",
                "localField" : "CAPID",
                "foreignField" : "customSchemas.Member.CAPID",
                "as" : "GoogleMember"
            }
        },
        {
            "$project" : {
                "CAPID" : "$CAPID",
                "Status" : "$Status",
                "Achv" : "$AchvID",
                "Email" : "$GoogleMember.primaryEmail"
            }
        },
        {
            "$unwind" : {
                "path" : "$Email",
                "preserveNullAndEmptyArrays" : false
            }
        }
];

// aggregation pipeline options
var options  = {
        "allowDiskUse" : false
    };

function isActiveMember( capid ) {
    // Check to see if member is active.
    // This function needs to be changed for each group depending
    // on what constitutes "active".
    var m = db.getCollection( "Member").findOne( { "CAPID": capid, "MbrStatus": "ACTIVE" } );
    if ( m == null ) { return false; }
    return true;

}

function addMembers( collection, pipeline, options, group ) {
    // Scans MbrAchivements looking for active/training member
    // if member is not currently on the mailing list generate gam command to add member.
    var cursor = collection.MbrAchievements.aggregate( pipeline, options );
    while ( cursor.hasNext() ) {
        var m = cursor.next();
        var email = m.Email.toLowerCase();
        var rx = new RegExp( email, 'i' );
        var g = db.getCollection( groupsCollection).findOne( { Group: group, Members: rx } );
        if ( g ) { continue; }
    // Print gam command to add new member
        print("gam update group", group, "add member", email );
    }
}

function isQualified( capid, quals ) {
    var m = db.getCollection( "MbrAchievements").findOne( { "CAPID": capid, "AchvID": {"$in": quals} , "$or": [ { "Status": "ACTIVE" }, { "Status": "TRAINING" } ] } );
    if ( m  ) { return true; }
    return false;
}

function removeMembers( group, options ){
    // for each member of the group make sure member is active
    // check qualification status active/training,
    // if not generate a gam command to remove member
    var cur = db.getCollection( 'GoogleGroups' ).find( { 'group': group } );
    while ( cur.hasNext() ) {
        var e = cur.next().email;
        var rgx = new RegExp( e, "i" );
        var  g = db.Google.findOne( {primaryEmail: rgx} );
        var capid = g.customSchemas.Member.CAPID;
        if ( isActiveMember( capid ) && isQualified( capid, achvIds ) ) { continue; }
	print( "gam update group", group, "remove member", e );
    }
}

// Main here
print("# Update group:", googleGroup );
print("# Add new members");
addMembers( Achvs, activePipeline, options, googleGroup );
print("# Remove inactive members");
removeMembers( googleGroup, options );
<commit_msg>removeMembers throws TypeError on accessing non-existent custom schema.<commit_after>// Find all AP's with ACTIVE or TRAINING status.  If not in aps group add,
// if not longer active remove.
var db = db.getSiblingDB( 'NHWG' );
var baseGroupName = 'aps';
var googleGroup = baseGroupName + '@nhwg.cap.gov';
var groupsCollection = 'GoogleGroups';
var Achvs = db.getCollection("MbrAchievements");
var achvIds = [ 193, ];
// This a left join
var activePipeline =  [
        {
            "$match" : {
                "AchvID" : { "$in": achvIds },
             }
        },
        {
            "$lookup" : {
                "from" : "Google",
                "localField" : "CAPID",
                "foreignField" : "customSchemas.Member.CAPID",
                "as" : "GoogleMember"
            }
        },
        {
            "$project" : {
                "CAPID" : "$CAPID",
                "Status" : "$Status",
                "Achv" : "$AchvID",
                "Email" : "$GoogleMember.primaryEmail"
            }
        },
        {
            "$unwind" : {
                "path" : "$Email",
                "preserveNullAndEmptyArrays" : false
            }
        }
];

// aggregation pipeline options
var options  = {
        "allowDiskUse" : false
    };

function isActiveMember( capid ) {
    // Check to see if member is active.
    // This function needs to be changed for each group depending
    // on what constitutes "active".
    var m = db.getCollection( "Member").findOne( { "CAPID": capid, "MbrStatus": "ACTIVE" } );
    if ( m == null ) { return false; }
    return true;

}

function addMembers( collection, pipeline, options, group ) {
    // Scans MbrAchivements looking for active/training member
    // if member is not currently on the mailing list generate gam command to add member.
    var cursor = collection.MbrAchievements.aggregate( pipeline, options );
    while ( cursor.hasNext() ) {
        var m = cursor.next();
        var email = m.Email.toLowerCase();
        var rx = new RegExp( email, 'i' );
        var g = db.getCollection( groupsCollection).findOne( { Group: group, Members: rx } );
        if ( g ) { continue; }
    // Print gam command to add new member
        print("gam update group", group, "add member", email );
    }
}

function isQualified( capid, quals ) {
    var m = db.getCollection( "MbrAchievements").findOne( { "CAPID": capid, "AchvID": {"$in": quals} , "$or": [ { "Status": "ACTIVE" }, { "Status": "TRAINING" } ] } );
    if ( m  ) { return true; }
    return false;
}

function removeMembers( group, options ){
    // for each member of the group make sure member is active
    // check qualification status active/training,
    // if not generate a gam command to remove member
    var cur = db.getCollection( 'GoogleGroups' ).find( { 'group': group,
							 role: 'MEMBER'} );
    while ( cur.hasNext() ) {
        var e = cur.next().email;
        var rgx = new RegExp( e, "i" );
        var  g = db.Google.findOne( {primaryEmail: rgx} );
        var capid = g.customSchemas.Member.CAPID;
        if ( isActiveMember( capid ) && isQualified( capid, achvIds ) ) { continue; }
	print( "gam update group", group, "remove member", e );
    }
}

// Main here
print("# Update group:", googleGroup );
print("# Add new members");
addMembers( Achvs, activePipeline, options, googleGroup );
print("# Remove inactive members");
removeMembers( googleGroup, options );
<|endoftext|>


<commit_before>// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

import Log from './Log';
import OidcClientSettings from './OidcClientSettings';
import MetadataService from './MetadataService';
import SigninRequest from './SigninRequest';
import SigninResponse from './SigninResponse';
import SignoutRequest from './SignoutRequest';
import SignoutResponse from './SignoutResponse';
import WebStorageStateStore from './WebStorageStateStore';
import ResponseValidator from './ResponseValidator';
import State from './State';

export default class OidcClient {
    constructor(settings,
        {
            stateStore = new WebStorageStateStore(),
            ResponseValidatorCtor = ResponseValidator,
            MetadataServiceCtor = MetadataService
        }
    ){
        if (!settings) {
            Log.error("No settings passed to OidcClient");
            throw new Error("settings");
        }

        if (settings instanceof OidcClientSettings){
            this._settings = settings;
        }
        else {
            this._settings = new OidcClientSettings(settings);
        }
        this._stateStore = stateStore;
        this._validator = new ResponseValidatorCtor(this._settings);
        this._metadataService = new MetadataServiceCtor(this._settings);
    }

    get settings() {
        return this._settings;
    }

    createSigninRequest({
        response_type, scope, redirect_uri, data,
        prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values}={}
    ) {
        Log.info("OidcClient.createSigninRequest");

        let client_id = this._settings.client_id;
        response_type = response_type || this._settings.response_type;
        scope = scope || this._settings.scope;
        redirect_uri = redirect_uri || this._settings.redirect_uri;

        // id_token_hint, login_hint aren't allowed on _settings
        prompt = prompt || this._settings.prompt;
        display = display || this._settings.display;
        max_age = max_age  || this._settings.max_age ;
        ui_locales = ui_locales || this._settings.ui_locales;
        acr_values = acr_values || this._settings.acr_values;

        return this._metadataService.getAuthorizationEndpoint().then(url => {
            Log.info("Received authorization endpoint", url);

            let request = new SigninRequest({url,
                client_id,
                redirect_uri,
                response_type,
                scope,
                data,
                prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values
            });

            var state = request.state;
            this._stateStore.set(state.id, state.toStorageString());

            return request;
        });
    }

    processSigninResponse(url){
        Log.info("OidcClient.processSigninResponse");

        var response = new SigninResponse(url);
        if (!response.state) {
            Log.error("No state in response");
            return Promise.reject(new Error("No state in response"));
        }

        var stateKey = response.state;

        return this._stateStore.remove(stateKey).then(storedStateString => {
            if (!storedStateString){
                Log.error("No matching state found in storage");
                throw new Error("No matching state found in storage");
            }

            let state = State.fromStorageString(storedStateString);

            Log.info("Received state from storage; validating response");
            return this._validator.validateSigninResponse(state, response);
        });
    }

    createSignoutRequest({id_token_hint, data, post_logout_redirect_uri}={}){
        Log.info("OidcClient.createSignoutRequest");

        post_logout_redirect_uri = post_logout_redirect_uri || this._settings.post_logout_redirect_uri;

        return this._metadataService.getEndSessionEndpoint().then(url => {
            Log.info("Received end session endpoint", url);

            let request = new SignoutRequest({url,
                id_token_hint,
                post_logout_redirect_uri,
                data
            });

            var state = request.state;
            this._stateStore.set(state.id, state.toStorageString());

            return request;
        });
    }

    processSignoutResponse(url){
        Log.info("OidcClient.processSignoutResponse");

        var response = new SignoutResponse(url);
        if (!response.state) {
            Log.error("No state in response");
            return Promise.reject(new Error("No state in response"));
        }

        var stateKey = response.state;

        return this._stateStore.remove(stateKey).then(storedStateString => {
            if (!storedStateString){
                Log.error("No matching state found in storage");
                throw new Error("No matching state found in storage");
            }

            let state = State.fromStorageString(storedStateString);

            Log.info("Received state from storage; validating response");
            return this._validator.validateSignoutResponse(state, response);
        });
    }

    clearStaleState(){
        Log.info("OidcClient.clearStaleState");
        return State.clearStaleState(this._stateStore, this.settings.staleStateAge);
    }
}
<commit_msg>allow store to be passed as param<commit_after>// Copyright (c) Brock Allen & Dominick Baier. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.

import Log from './Log';
import OidcClientSettings from './OidcClientSettings';
import MetadataService from './MetadataService';
import SigninRequest from './SigninRequest';
import SigninResponse from './SigninResponse';
import SignoutRequest from './SignoutRequest';
import SignoutResponse from './SignoutResponse';
import WebStorageStateStore from './WebStorageStateStore';
import ResponseValidator from './ResponseValidator';
import State from './State';

export default class OidcClient {
    constructor(settings,
        {
            stateStore = new WebStorageStateStore(),
            ResponseValidatorCtor = ResponseValidator,
            MetadataServiceCtor = MetadataService
        }
    ){
        if (!settings) {
            Log.error("No settings passed to OidcClient");
            throw new Error("settings");
        }

        if (settings instanceof OidcClientSettings){
            this._settings = settings;
        }
        else {
            this._settings = new OidcClientSettings(settings);
        }
        this._stateStore = stateStore;
        this._validator = new ResponseValidatorCtor(this._settings);
        this._metadataService = new MetadataServiceCtor(this._settings);
    }

    get settings() {
        return this._settings;
    }

    createSigninRequest({
        response_type, scope, redirect_uri, data,
        prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values}={},
        stateStore
    ) {
        Log.info("OidcClient.createSigninRequest");

        stateStore = stateStore || this._stateStore;

        let client_id = this._settings.client_id;
        response_type = response_type || this._settings.response_type;
        scope = scope || this._settings.scope;
        redirect_uri = redirect_uri || this._settings.redirect_uri;

        // id_token_hint, login_hint aren't allowed on _settings
        prompt = prompt || this._settings.prompt;
        display = display || this._settings.display;
        max_age = max_age  || this._settings.max_age ;
        ui_locales = ui_locales || this._settings.ui_locales;
        acr_values = acr_values || this._settings.acr_values;

        return this._metadataService.getAuthorizationEndpoint().then(url => {
            Log.info("Received authorization endpoint", url);

            let request = new SigninRequest({url,
                client_id,
                redirect_uri,
                response_type,
                scope,
                data,
                prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values
            });

            var state = request.state;
            stateStore.set(state.id, state.toStorageString());

            return request;
        });
    }

    processSigninResponse(url, stateStore){
        Log.info("OidcClient.processSigninResponse");

        stateStore = stateStore || this._stateStore;

        var response = new SigninResponse(url);
        if (!response.state) {
            Log.error("No state in response");
            return Promise.reject(new Error("No state in response"));
        }

        var stateKey = response.state;

        return stateStore.remove(stateKey).then(storedStateString => {
            if (!storedStateString){
                Log.error("No matching state found in storage");
                throw new Error("No matching state found in storage");
            }

            let state = State.fromStorageString(storedStateString);

            Log.info("Received state from storage; validating response");
            return this._validator.validateSigninResponse(state, response);
        });
    }

    createSignoutRequest({id_token_hint, data, post_logout_redirect_uri}={},
        stateStore
    ){
        Log.info("OidcClient.createSignoutRequest");

        stateStore = stateStore || this._stateStore;

        post_logout_redirect_uri = post_logout_redirect_uri || this._settings.post_logout_redirect_uri;

        return this._metadataService.getEndSessionEndpoint().then(url => {
            Log.info("Received end session endpoint", url);

            let request = new SignoutRequest({url,
                id_token_hint,
                post_logout_redirect_uri,
                data
            });

            var state = request.state;
            stateStore.set(state.id, state.toStorageString());

            return request;
        });
    }

    processSignoutResponse(url, stateStore){
        Log.info("OidcClient.processSignoutResponse");

        stateStore = stateStore || this._stateStore;

        var response = new SignoutResponse(url);
        if (!response.state) {
            Log.error("No state in response");
            return Promise.reject(new Error("No state in response"));
        }

        var stateKey = response.state;

        return stateStore.remove(stateKey).then(storedStateString => {
            if (!storedStateString){
                Log.error("No matching state found in storage");
                throw new Error("No matching state found in storage");
            }

            let state = State.fromStorageString(storedStateString);

            Log.info("Received state from storage; validating response");
            return this._validator.validateSignoutResponse(state, response);
        });
    }

    clearStaleState(stateStore){
        Log.info("OidcClient.clearStaleState");

        stateStore = stateStore || this._stateStore;

        return State.clearStaleState(stateStore, this.settings.staleStateAge);
    }
}
<|endoftext|>


<commit_before>const EventEmitter = require('events').EventEmitter
const pMap = require('p-map')
const Log = require('ipfs-log')

const Logger = require('logplease')
const logger = Logger.create('replicator', { color: Logger.Colors.Cyan })
Logger.setLogLevel('ERROR')

const getNext = e => e.next
const flatMap = (res, val) => res.concat(val)
const notNull = entry => entry !== null && entry !== undefined
const uniqueValues = (res, val) => {
  res[val] = val
  return res
}

const batchSize = 1

class Replicator extends EventEmitter {
  constructor (store, concurrency) {
    super()
    this._store = store
    this._fetching = {}
    this._stats = {
      tasksRequested: 0,
      tasksStarted: 0,
      tasksProcessed: 0
    }
    this._buffer = []

    this._concurrency = concurrency || 128
    this._queue = {}
    this._q = new Set()

    // Flush the queue as an emergency switch
    this._flushTimer = setInterval(() => {
      if (this.tasksRunning === 0 && Object.keys(this._queue).length > 0) {
        logger.warn('Had to flush the queue!', Object.keys(this._queue).length, 'items in the queue, ', this.tasksRequested, this.tasksFinished, ' tasks requested/finished')
        setTimeout(() => this._processQueue(), 0)
      }
    }, 3000)
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks started]
   */
  get tasksRequested () {
    return this._stats.tasksRequested
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksStarted () {
    return this._stats.tasksStarted
  }

  /**
   * Returns the number of tasks running currently
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksRunning () {
    return this._stats.tasksStarted - this._stats.tasksProcessed
  }

  /**
   * Returns the number of tasks currently queued
   * @return {[Integer]} [Number of tasks queued]
   */
  get tasksQueued () {
    return Math.max(Object.keys(this._queue).length - this.tasksRunning, 0)
  }

  /**
   * Returns the number of tasks finished during the life time
   * @return {[Integer]} [Number of tasks finished]
   */
  get tasksFinished () {
    return this._stats.tasksProcessed
  }

  /**
   * Returns the hashes currently queued
   * @return {[Array<String>]} [Queued hashes]
   */
  getQueue () {
    return Object.values(this._queue)
  }

  /*
    Process new heads.
   */
  load (entries) {
    const notKnown = entry => {
      const hash = entry.hash || entry
      return !this._store._oplog.has(hash) && !this._fetching[hash] && !this._queue[hash]
    }

    try {
      entries
        .filter(notNull)
        .filter(notKnown)
        .forEach(this._addToQueue.bind(this))

      setTimeout(() => this._processQueue(), 0)
    } catch (e) {
      console.error(e)
    }
  }

  stop () {
    // Clears the queue flusher
    clearInterval(this._flushTimer)
  }

  _addToQueue (entry) {
    const hash = entry.hash || entry
    this._stats.tasksRequested += 1
    this._queue[hash] = entry
  }

  async _processQueue () {
    if (this.tasksRunning < this._concurrency) {
      const capacity = this._concurrency - this.tasksRunning
      const items = Object.values(this._queue).slice(0, capacity).filter(notNull)
      items.forEach(entry => delete this._queue[entry.hash || entry])

      const flattenAndGetUniques = (nexts) => nexts.reduce(flatMap, []).reduce(uniqueValues, {})
      const processValues = (nexts) => {
        const values = Object.values(nexts).filter(notNull)

        if ((items.length > 0 && this._buffer.length > 0) ||
        (this.tasksRunning === 0 && this._buffer.length > 0)) {
          const logs = this._buffer.slice()
          this._buffer = []
          this.emit('load.end', logs)
        }

        if (values.length > 0) {
          this.load(values)
        }
      }

      return pMap(items, e => this._processOne(e))
        .then(flattenAndGetUniques)
        .then(processValues)
    }
  }

  async _processOne (entry) {
    const hash = entry.hash || entry

    if (this._store._oplog.has(hash) || this._fetching[hash]) {
      return
    }

    this._fetching[hash] = hash
    this.emit('load.added', entry)
    this._stats.tasksStarted += 1

    const exclude = []
    const log = await Log.fromEntryHash(this._store._ipfs, this._store.identity, hash, { logId: this._store._oplog.id, access: this._store.access, length: batchSize, exclude })
    this._buffer.push(log)

    const latest = log.values[0]
    delete this._queue[hash]

    // Mark this task as processed
    this._stats.tasksProcessed += 1

    // Notify subscribers that we made progress
    this.emit('load.progress', this._id, hash, latest, null, this._buffer.length)

    // Return all next pointers
    return log.values.map(getNext).reduce(flatMap, [])
  }
}

module.exports = Replicator
<commit_msg>Remove event listeners upon stopping the replicator<commit_after>const EventEmitter = require('events').EventEmitter
const pMap = require('p-map')
const Log = require('ipfs-log')

const Logger = require('logplease')
const logger = Logger.create('replicator', { color: Logger.Colors.Cyan })
Logger.setLogLevel('ERROR')

const getNext = e => e.next
const flatMap = (res, val) => res.concat(val)
const notNull = entry => entry !== null && entry !== undefined
const uniqueValues = (res, val) => {
  res[val] = val
  return res
}

const batchSize = 1

class Replicator extends EventEmitter {
  constructor (store, concurrency) {
    super()
    this._store = store
    this._fetching = {}
    this._stats = {
      tasksRequested: 0,
      tasksStarted: 0,
      tasksProcessed: 0
    }
    this._buffer = []

    this._concurrency = concurrency || 128
    this._queue = {}
    this._q = new Set()

    // Flush the queue as an emergency switch
    this._flushTimer = setInterval(() => {
      if (this.tasksRunning === 0 && Object.keys(this._queue).length > 0) {
        logger.warn('Had to flush the queue!', Object.keys(this._queue).length, 'items in the queue, ', this.tasksRequested, this.tasksFinished, ' tasks requested/finished')
        setTimeout(() => this._processQueue(), 0)
      }
    }, 3000)
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks started]
   */
  get tasksRequested () {
    return this._stats.tasksRequested
  }

  /**
   * Returns the number of tasks started during the life time
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksStarted () {
    return this._stats.tasksStarted
  }

  /**
   * Returns the number of tasks running currently
   * @return {[Integer]} [Number of tasks running]
   */
  get tasksRunning () {
    return this._stats.tasksStarted - this._stats.tasksProcessed
  }

  /**
   * Returns the number of tasks currently queued
   * @return {[Integer]} [Number of tasks queued]
   */
  get tasksQueued () {
    return Math.max(Object.keys(this._queue).length - this.tasksRunning, 0)
  }

  /**
   * Returns the number of tasks finished during the life time
   * @return {[Integer]} [Number of tasks finished]
   */
  get tasksFinished () {
    return this._stats.tasksProcessed
  }

  /**
   * Returns the hashes currently queued
   * @return {[Array<String>]} [Queued hashes]
   */
  getQueue () {
    return Object.values(this._queue)
  }

  /*
    Process new heads.
   */
  load (entries) {
    const notKnown = entry => {
      const hash = entry.hash || entry
      return !this._store._oplog.has(hash) && !this._fetching[hash] && !this._queue[hash]
    }

    try {
      entries
        .filter(notNull)
        .filter(notKnown)
        .forEach(this._addToQueue.bind(this))

      setTimeout(() => this._processQueue(), 0)
    } catch (e) {
      console.error(e)
    }
  }

  stop () {
    // Clears the queue flusher
    clearInterval(this._flushTimer)
    // Remove event listeners
    this.removeAllListeners('load.added')
    this.removeAllListeners('load.end')
    this.removeAllListeners('load.progress')
  }

  _addToQueue (entry) {
    const hash = entry.hash || entry
    this._stats.tasksRequested += 1
    this._queue[hash] = entry
  }

  async _processQueue () {
    if (this.tasksRunning < this._concurrency) {
      const capacity = this._concurrency - this.tasksRunning
      const items = Object.values(this._queue).slice(0, capacity).filter(notNull)
      items.forEach(entry => delete this._queue[entry.hash || entry])

      const flattenAndGetUniques = (nexts) => nexts.reduce(flatMap, []).reduce(uniqueValues, {})
      const processValues = (nexts) => {
        const values = Object.values(nexts).filter(notNull)

        if ((items.length > 0 && this._buffer.length > 0) ||
        (this.tasksRunning === 0 && this._buffer.length > 0)) {
          const logs = this._buffer.slice()
          this._buffer = []
          this.emit('load.end', logs)
        }

        if (values.length > 0) {
          this.load(values)
        }
      }

      return pMap(items, e => this._processOne(e))
        .then(flattenAndGetUniques)
        .then(processValues)
    }
  }

  async _processOne (entry) {
    const hash = entry.hash || entry

    if (this._store._oplog.has(hash) || this._fetching[hash]) {
      return
    }

    this._fetching[hash] = hash
    this.emit('load.added', entry)
    this._stats.tasksStarted += 1

    const exclude = []
    const log = await Log.fromEntryHash(this._store._ipfs, this._store.identity, hash, { logId: this._store._oplog.id, access: this._store.access, length: batchSize, exclude })
    this._buffer.push(log)

    const latest = log.values[0]
    delete this._queue[hash]

    // Mark this task as processed
    this._stats.tasksProcessed += 1

    // Notify subscribers that we made progress
    this.emit('load.progress', this._id, hash, latest, null, this._buffer.length)

    // Return all next pointers
    return log.values.map(getNext).reduce(flatMap, [])
  }
}

module.exports = Replicator
<|endoftext|>


<commit_before>var test = require('tap').test
    , Queue = require('fastqueue')
;

/**
 * We're using some internals so lets make sure they're not different
 * than when we wrote this.
 */
test('require', function(t) {
    var q;

    t.ok(Queue, 'fastqueue exists');
    t.type(Queue, 'function', 'require returns an object');
    t.equal(0, Object.keys(Queue).length, 'No hidden exports');

    q = new Queue();

    t.type(q, 'object', 'new Queue() returns an object');
    t.type(q.length, 'number', 'q.length');

    t.ok(Array.isArray(q.head), 'q.head = []');
    t.ok(Array.isArray(q.tail), 'q.tail = []');

    q.push(1);

    t.equal(1, q.length, 'push(length == 1)');
    t.equal(1, q.shift(), 'shift');
    t.equal(0, q.length, 'length == 0');

    t.equal(undefined, q.shift(), 'empty shift === undefined');

    t.end();
});
<commit_msg>0.js :: switch from tap to tape<commit_after>var test = require('tape')
    , Queue = require('fastqueue')
;

/**
 * We're using some internals so lets make sure they're not different
 * than when we wrote this.
 */
test('require', function(t) {
    var q;

    t.ok(Queue, 'fastqueue exists');
    t.equal(typeof(Queue), 'function', 'require returns an object');
    t.equal(0, Object.keys(Queue).length, 'No hidden exports');

    q = new Queue();

    t.equal(typeof(q), 'object', 'new Queue() returns an object');
    t.equal(typeof(q.length), 'number', 'q.length');

    t.ok(Array.isArray(q.head), 'q.head = []');
    t.ok(Array.isArray(q.tail), 'q.tail = []');

    q.push(1);

    t.equal(1, q.length, 'push(length == 1)');
    t.equal(1, q.shift(), 'shift');
    t.equal(0, q.length, 'length == 0');

    t.equal(undefined, q.shift(), 'empty shift === undefined');

    t.end();
});
<|endoftext|>


<commit_before>var mock_obj = {
    "0" : { // id = 0 is alway "you"
      "id" : "0", // must match the obj hash key
      "img" : "redship.png",
      "type" : "ship",
      "team" : 0, // useful for friendly fire
// origin_id tells you where the object (usually a projectile) came from
      "scale" : 1,
      "shield" : 1,
      "hull" : 1,
      "energy" : 1,
      "x" : 0,
      "y" : 0,
      "move_radians" : 0, // direction I am moving
      "obj_radians" : 0, // direction I am facing
      "obj_speed" : 123 // current speed
    },
    "1" : {
      "id" : "1",
      "img" : "greyship.png",
      "type" : "ship",
      "team" : 1,
      "scale" : 1,
      "hull" : 1,
      "shield" : 1,
      "energy" : 1,
      "x" : 100,
      "y" : -100,
      "move_radians" : 0,
      "obj_radians" : 0,
      "obj_speed" : 123
    },
    "3" : {
      "id" : "3",
      "img" : "ship.png",
      "type" : "ship",
      "team" : 1,
      "scale" : 1,
      "hull" : 1,
      "shield" : 1,
      "energy" : 1,
      "x" : 450,
      "y" : 250,
      "move_radians" : 0,
      "obj_radians" : 0,
      "obj_speed" : 123
    },
    "4" : {
      "id" : "4",
      "img" : "blueship.png",
      "type" : "ship",
      "team" : 1,
      "scale" : 1,
      "hull" : 1,
      "shield" : 1,
      "energy" : 1,
      "x" : -50,
      "y" : -10,
      "move_radians" : 0,
      "obj_radians" : 0,
      "obj_speed" : 123
    }
};

function mock_server_long() {
  return {
    "5" : 3,
    "6" : 1,
    "7" : 2,
    "8" : 1.5,
  };
}

mock_cnt=0;
function mock_server() {
  // update the json with the new location
  //
  // currently ignoring obj_speed
  mock_cnt = mock_cnt+1;

  obj = mock_obj[0];
  obj.move_radians = obj.move_radians + Math.PI/15;
  obj.obj_radians = obj.move_radians; // simple

  obj = mock_obj[1];
  obj.move_radians = obj.move_radians + Math.PI/6;
  obj.obj_radians = obj.move_radians; // simple
  obj.x = obj.x + Math.cos(obj.move_radians) * 350;
  obj.y = obj.y + Math.sin(obj.move_radians) * 350;

  obj = mock_obj[2];
if (mock_cnt == 11) {
  mock_obj[2].img = 'explosion1.png';
  mock_obj[0].shield = 0.5;
} else if (mock_cnt >= 12) {
  delete mock_obj[2];
} else if (obj) {
  obj.move_radians = obj.move_radians - Math.PI/6;
  obj.obj_radians = obj.move_radians; // simple
  obj.x = obj.x + Math.cos(obj.move_radians) * 150;
  obj.y = obj.y + Math.sin(obj.move_radians) * 50;
} else if (mock_cnt > 5) {
  obj = {
    "id" : "2",
    "origin_id" : "4", // where the object came from
    "img" : "missile.png",
    "type" : "projectile",
    "team" : 1,
    "scale" : 0.5,
    "x" : mock_obj[4].x - 50,
    "y" : mock_obj[4].y + 50,
    "move_radians" : mock_obj[4].obj_radians,
    "obj_radians" : mock_obj[4].obj_radians,
    "obj_speed" : 123
  };
  mock_obj[2] = obj;
}

  obj = mock_obj[3];
  obj.move_radians = obj.move_radians - Math.PI/9;
  obj.obj_radians = obj.move_radians; // simple
  obj.x = obj.x + Math.cos(obj.move_radians) * 350;
  obj.y = obj.y + Math.sin(obj.move_radians) * 350;

  obj = mock_obj[4];
  obj.move_radians = obj.move_radians - Math.PI/9;
  obj.obj_radians = obj.move_radians - Math.PI/2; // fly sideways
  obj.x = obj.x + Math.cos(obj.move_radians) * 50;
  obj.y = obj.y + Math.sin(obj.move_radians) * 50;

  if (Math.random() > 0.88) {
    json.logs = ['This is a random message '+Math.random(),'Message 2'];
  }
  return mock_obj;
}
<commit_msg>Simulate energy usage as well as shield/hull damage/repair<commit_after>var mock_obj = {
    "0" : { // id = 0 is alway "you"
      "id" : "0", // must match the obj hash key
      "img" : "redship.png",
      "type" : "ship",
      "team" : 0, // useful for friendly fire
// origin_id tells you where the object (usually a projectile) came from
      "scale" : 1,
      "shield" : 1,
      "hull" : 1,
      "energy" : 1,
      "x" : 0,
      "y" : 0,
      "move_radians" : 0, // direction I am moving
      "obj_radians" : 0, // direction I am facing
      "obj_speed" : 123 // current speed
    },
    "1" : {
      "id" : "1",
      "img" : "greyship.png",
      "type" : "ship",
      "team" : 1,
      "scale" : 1,
      "hull" : 1,
      "shield" : 1,
      "energy" : 1,
      "x" : 100,
      "y" : -100,
      "move_radians" : 0,
      "obj_radians" : 0,
      "obj_speed" : 123
    },
    "3" : {
      "id" : "3",
      "img" : "ship.png",
      "type" : "ship",
      "team" : 1,
      "scale" : 1,
      "hull" : 1,
      "shield" : 1,
      "energy" : 1,
      "x" : 450,
      "y" : 250,
      "move_radians" : 0,
      "obj_radians" : 0,
      "obj_speed" : 123
    },
    "4" : {
      "id" : "4",
      "img" : "blueship.png",
      "type" : "ship",
      "team" : 1,
      "scale" : 1,
      "hull" : 1,
      "shield" : 1,
      "energy" : 1,
      "x" : -50,
      "y" : -10,
      "move_radians" : 0,
      "obj_radians" : 0,
      "obj_speed" : 123
    }
};

function mock_server_long() {
  return {
    "5" : 3,
    "6" : 1,
    "7" : 2,
    "8" : 1.5,
  };
}

mock_cnt=0;
function mock_server() {
  // update the json with the new location
  //
  // currently ignoring obj_speed
  mock_cnt = mock_cnt+1;

  obj = mock_obj[0];
  obj.move_radians = obj.move_radians + Math.PI/15;
  obj.obj_radians = obj.move_radians; // simple
  if (obj.energy < 1) {
    obj.energy = obj.energy + 0.1;
    if (obj.energy > 1) {
      obj.energy = 1;
    }
  }
  if (obj.shield < 1 && obj.energy >= 0.1) {
    obj.shield = obj.shield + 0.05;
    obj.energy = obj.energy - 0.07;
    if (obj.shield > 1) {
      obj.shield = 1;
    }
  }
  if (obj.hull < 1 && obj.energy >= 0.1) {
    obj.hull = obj.hull + 0.01;
    obj.energy = obj.energy - 0.07;
    if (obj.hull > 1) {
      obj.hull = 1;
    }
  }

  obj = mock_obj[1];
  obj.move_radians = obj.move_radians + Math.PI/6;
  obj.obj_radians = obj.move_radians; // simple
  obj.x = obj.x + Math.cos(obj.move_radians) * 350;
  obj.y = obj.y + Math.sin(obj.move_radians) * 350;

  obj = mock_obj[2];
if (mock_cnt == 11) {
  mock_obj[2].img = 'explosion1.png';
  mock_obj[0].shield = 0.5;
  mock_obj[0].hull = 0.5;
} else if (mock_cnt >= 12) {
  delete mock_obj[2];
} else if (obj) {
  obj.move_radians = obj.move_radians - Math.PI/6;
  obj.obj_radians = obj.move_radians; // simple
  obj.x = obj.x + Math.cos(obj.move_radians) * 150;
  obj.y = obj.y + Math.sin(obj.move_radians) * 50;
} else if (mock_cnt > 5) {
  obj = {
    "id" : "2",
    "origin_id" : "4", // where the object came from
    "img" : "missile.png",
    "type" : "projectile",
    "team" : 1,
    "scale" : 0.5,
    "x" : mock_obj[4].x - 50,
    "y" : mock_obj[4].y + 50,
    "move_radians" : mock_obj[4].obj_radians,
    "obj_radians" : mock_obj[4].obj_radians,
    "obj_speed" : 123
  };
  mock_obj[2] = obj;
  mock_obj[4].energy = 0.5;
}

  obj = mock_obj[3];
  obj.move_radians = obj.move_radians - Math.PI/9;
  obj.obj_radians = obj.move_radians; // simple
  obj.x = obj.x + Math.cos(obj.move_radians) * 350;
  obj.y = obj.y + Math.sin(obj.move_radians) * 350;

  obj = mock_obj[4];
  obj.move_radians = obj.move_radians - Math.PI/9;
  obj.obj_radians = obj.move_radians - Math.PI/2; // fly sideways
  obj.x = obj.x + Math.cos(obj.move_radians) * 50;
  obj.y = obj.y + Math.sin(obj.move_radians) * 50;
  if (obj.energy < 1) {
    obj.energy = obj.energy + 0.1;
    if (obj.energy > 1) {
      obj.energy = 1;
    }
  }

  if (Math.random() > 0.88) {
    json.logs = ['This is a random message '+Math.random(),'Message 2'];
  }
  return mock_obj;
}
<|endoftext|>


<commit_before>// https://github.com/nuxt/nuxt.js/blob/dev/lib/common/options.js#L170
// console.log(process.env.NODE_ENV) // npm run dev: 'development', npm run build: 'production', npm run generate: 'production'
// const webpack = require('webpack')
module.exports = {
  /*
  ** Build configuration
  */
  build: {
    // analyze: true,
    extractCSS: true,
    vendor: ['common-tags', 'uuid/v1'], // 會被包在 common.[hash].js 裡面
    plugins: [
      // new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // 使用 moment without locale
      // new webpack.optimize.CommonsChunkPlugin({ // 在各 .vue 裡可視情況分類套件並載入
      //   async: 'common-in-lazy',
      //   minChunks: ({ resource } = {}) => (
      //     resource &&
      //     resource.includes('node_modules') &&
      //     (/moment/.test(resource) || /accounting/.test(resource))
      //   )
      // })
    ]
  },
  /*
  ** Headers
  ** Common headers are already provided by @nuxtjs/pwa preset
  */
  head: {
    htmlAttrs: {
      lang: 'zh-Hant-TW'
    },
    title: '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'keywords', name: 'keywords', content: 'fund程式,模擬基金投資,基金競賽,基金比賽' },
      { hid: 'description', name: 'description', content: '' },
      { property: 'og:type', content: 'website' },
      { property: 'og:site_name', content: '中華民國證券投資信託暨顧問商業同業公會' },
      { hid: 'og:url', property: 'og:url', content: process.env.NODE_ENV === 'development' ? 'http://172.18.1.82:3000/' : 'http://taiwanfund.event-fundrich.com/' },
      { hid: 'og:title', property: 'og:title', content: '' },
      { hid: 'og:description', property: 'og:description', content: '' },
      { hid: 'og:image', property: 'og:image', content: process.env.NODE_ENV === 'development' ? 'http://172.18.1.82:3000/og.jpg' : 'http://taiwanfund.event-fundrich.com/og.jpg' },
      { hid: 'og:image:type', property: 'og:image:type', content: 'image/jpeg' },
      { property: 'og:image:width', content: '1200' },
      { property: 'og:image:height', content: '628' },
      { 'http-equiv': 'cache-control', content: 'max-age=0' } // 活動站需要時時更新，從 client 端指定沒有 cache 週期
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: './favicon.ico' },
      { rel: 'icon', type: 'image/png', href: './favicon.png' }
    ]
  },
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#269384' },
  /*
  ** Customize app manifest
  */
  meta: {
    ogTitle: false, // 設定為 false，就不會跟頁面中的 head: { meta: [{ hid: 'og:title' }] } 資訊重複
    ogDescription: false // 設定為 false，就不會跟頁面中的 head: { meta: [{ hid: 'og:description' }] } 資訊重複
  },
  manifest: {
    start_url: 'http://taiwanfund.event-fundrich.com/?utm_source=web_app_manifest&utm_campaign=2017_taiwanfund_race',
    name: '創造勝利 fund 程式',
    short_name: 'fund 程式',
    background_color: '#FFF', // splash screen 背景色
    theme_color: '#269384', // 狀態列顏色
    orientation: 'portrait', // portrait: 直式, landscape: 橫式
    description: '全國大專院校基金投資模擬競賽',
    lang: 'zh-Hant-TW'
  },
  /*
  ** CSS
  */
  css: [
    'normalize.css',
    'hamburgers/dist/hamburgers.min.css',
    '@/assets/sass/main.sass'
  ],
  /*
  ** Plugins
  */
  plugins: [
    // 這裡只是註明 server 會不會用到這些 plugins, 為了省記憶體
    // 如果沒有將套件寫在 vendor 的設定裡, 就是包在各自的頁面裡
    // 除非利用 webpack.optimize.CommonsChunkPlugin, 才會再包在 common-in-lazy.[hash].js 裡
    { src: '@/plugins/velocity', ssr: false }
  ],
  /*
  ** Modules
  */
  modules: [
    // 自製 modules 時機：會利用到 NUXT 的 life circle, 或是想把一些邏輯當範本使用
    '@nuxtjs/pwa',
    // ['@nuxtjs/google-tag-manager', { id: 'GTM-KV9327S' }],
    '@/modules/tapable',
    '@/modules/custom-loaders'
  ],
  /*
  ** Single page application is served under "./"
  */
  router: { base: '/' },
  /*
  ** Customize runtime options for rendering pages
  */
  render: {
    // 尚無法得知此以下設定是否有效
    // bundleRenderer: {
    //   shouldPreload: (file, type) => ['script', 'style', 'font'].includes(type)
    // },
    http2: { push: true },
    static: { maxAge: '15m' },
    gzip: { threshold: 9 }
  }
}
<commit_msg>參數的設定方法<commit_after>// https://github.com/nuxt/nuxt.js/blob/dev/lib/common/options.js#L170
// console.log(process.env.NODE_ENV) // npm run dev: 'development', npm run build: 'production', npm run generate: 'production'
// const webpack = require('webpack')
module.exports = {
  /*
  ** Build configuration
  */
  build: {
    // analyze: true,
    extractCSS: true,
    vendor: ['common-tags', 'uuid/v1'], // 會被包在 common.[hash].js 裡面
    plugins: [
      // new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // 使用 moment without locale
      // new webpack.optimize.CommonsChunkPlugin({ // 在各 .vue 裡可視情況分類套件並載入
      //   async: 'common-in-lazy',
      //   minChunks: ({ resource } = {}) => (
      //     resource &&
      //     resource.includes('node_modules') &&
      //     (/moment/.test(resource) || /accounting/.test(resource))
      //   )
      // })
    ]
  },
  /*
  ** Create environment variables that will be shared for the client and server-side.
  ** 可在此設定任何參數, 任何一處 .js 都可利用 process.env 取得設定
  ** 但要注意不可以直接在 pug 中使用, 因為 pug 是預設使用 this 中的資料
  ** 必須要先在 data 中建立參數, 再把 process.env 中的參數指定到 data 中
  */
  env: {},
  /*
  ** Headers
  ** Common headers are already provided by @nuxtjs/pwa preset
  */
  head: {
    htmlAttrs: {
      lang: 'zh-Hant-TW'
    },
    title: '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'keywords', name: 'keywords', content: 'fund程式,模擬基金投資,基金競賽,基金比賽' },
      { hid: 'description', name: 'description', content: '' },
      { property: 'og:type', content: 'website' },
      { property: 'og:site_name', content: '中華民國證券投資信託暨顧問商業同業公會' },
      { hid: 'og:url', property: 'og:url', content: process.env.NODE_ENV === 'development' ? 'http://172.18.1.82:3000/' : 'http://taiwanfund.event-fundrich.com/' },
      { hid: 'og:title', property: 'og:title', content: '' },
      { hid: 'og:description', property: 'og:description', content: '' },
      { hid: 'og:image', property: 'og:image', content: process.env.NODE_ENV === 'development' ? 'http://172.18.1.82:3000/og.jpg' : 'http://taiwanfund.event-fundrich.com/og.jpg' },
      { hid: 'og:image:type', property: 'og:image:type', content: 'image/jpeg' },
      { property: 'og:image:width', content: '1200' },
      { property: 'og:image:height', content: '628' },
      { 'http-equiv': 'cache-control', content: 'max-age=0' } // 活動站需要時時更新，從 client 端指定沒有 cache 週期
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: './favicon.ico' },
      { rel: 'icon', type: 'image/png', href: './favicon.png' }
    ]
  },
  /*
  ** Customize the progress-bar color
  */
  loading: { color: '#269384' },
  /*
  ** Customize app manifest
  */
  meta: {
    ogTitle: false, // 設定為 false，就不會跟頁面中的 head: { meta: [{ hid: 'og:title' }] } 資訊重複
    ogDescription: false // 設定為 false，就不會跟頁面中的 head: { meta: [{ hid: 'og:description' }] } 資訊重複
  },
  manifest: {
    start_url: 'http://taiwanfund.event-fundrich.com/?utm_source=web_app_manifest&utm_campaign=2017_taiwanfund_race',
    name: '創造勝利 fund 程式',
    short_name: 'fund 程式',
    background_color: '#FFF', // splash screen 背景色
    theme_color: '#269384', // 狀態列顏色
    orientation: 'portrait', // portrait: 直式, landscape: 橫式
    description: '全國大專院校基金投資模擬競賽',
    lang: 'zh-Hant-TW'
  },
  /*
  ** CSS
  */
  css: [
    'normalize.css',
    'hamburgers/dist/hamburgers.min.css',
    '@/assets/sass/main.sass'
  ],
  /*
  ** Plugins
  */
  plugins: [
    // 這裡只是註明 server 會不會用到這些 plugins, 為了省記憶體
    // 如果沒有將套件寫在 vendor 的設定裡, 就是包在各自的頁面裡
    // 除非利用 webpack.optimize.CommonsChunkPlugin, 才會再包在 common-in-lazy.[hash].js 裡
    { src: '@/plugins/velocity', ssr: false }
  ],
  /*
  ** Modules
  */
  modules: [
    // 自製 modules 時機：會利用到 NUXT 的 life circle, 或是想把一些邏輯當範本使用
    '@nuxtjs/pwa',
    // ['@nuxtjs/google-tag-manager', { id: 'GTM-KV9327S' }],
    '@/modules/tapable',
    '@/modules/custom-loaders'
  ],
  /*
  ** Single page application is served under "./"
  */
  router: { base: '/' },
  /*
  ** Customize runtime options for rendering pages
  */
  render: {
    // 尚無法得知此以下設定是否有效
    // bundleRenderer: {
    //   shouldPreload: (file, type) => ['script', 'style', 'font'].includes(type)
    // },
    http2: { push: true },
    static: { maxAge: '15m' },
    gzip: { threshold: 9 }
  }
}
<|endoftext|>