<commit_before>/**
 * TagmanagerSetup component.
 *
 * Site Kit by Google, Copyright 2019 Google LLC
 *
 * 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
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Button from 'GoogleComponents/button';
import Link from 'GoogleComponents/link';
import data from 'GoogleComponents/data';
import ProgressBar from 'GoogleComponents/progress-bar';
import { Select, Option } from 'SiteKitCore/material-components';
import SvgIcon from 'GoogleUtil/svg-icon';
import PropTypes from 'prop-types';
import { toggleConfirmModuleSettings } from 'GoogleUtil';

const { __ } = wp.i18n;
const { Component, Fragment } = wp.element;
const {
	removeFilter,
	addFilter,
} = wp.hooks;

class TagmanagerSetup extends Component {

	constructor( props ) {
		super( props );

		const {
			accountId,
			containerId,
		} = googlesitekit.modules.tagmanager.settings;

		this.state = {
			isLoading: true,
			accounts: [],
			containers: [],
			errorCode: false,
			errorMsg: '',
			refetch: false,
			selectedAccount: accountId ? accountId : 0,
			selectedContainer: containerId ? containerId : 0,
			containersLoading: false,
		};

		this.handleSubmit = this.handleSubmit.bind( this );
		this.renderAccountDropdownForm = this.renderAccountDropdownForm.bind( this );
		this.handleAccountChange = this.handleAccountChange.bind( this );
		this.handleContainerChange = this.handleContainerChange.bind( this );
		this.refetchAccount = this.refetchAccount.bind( this );

	}

	componentDidMount() {
		this._isMounted = true;
		this.requestTagManagerAccounts();

		// Handle save hook from the settings page.
		addFilter( 'googlekit.SettingsConfirmed',
			'googlekit.TagmanagerSettingsConfirmed',
			( chain, module ) => {
				if ( 'tagmanager' !== module.replace( '-module', '' ) ) {
					return chain;
				}
				const { isEditing } = this.props;
				if ( isEditing ) {
					return this.handleSubmit();
				}
			} );

		this.toggleConfirmChangesButton();
	}

	componentDidUpdate() {
		const { refetch } = this.state;

		if ( refetch ) {
			this.requestTagManagerAccounts();
		}

		this.toggleConfirmChangesButton();
	}

	componentWillUnmount() {
		this._isMounted = false;

		removeFilter( 'googlekit.SettingsConfirmed', 'googlekit.TagmanagerSettingsConfirmed' );
	}

	/**
	 * Toggle confirm changes button disable/enable depending on the changed settings.
	 */
	toggleConfirmChangesButton() {

		if ( ! this.props.isEditing ) {
			return;
		}

		const settingsMapping = {
			selectedContainer: 'containerId',
			selectedAccount: 'selectedAccount',
		};

		toggleConfirmModuleSettings( 'tagmanager', settingsMapping, this.state );
	}

	/**
	 * Request Tag Manager accounts.
	 */
	async requestTagManagerAccounts() {

		try {
			const {
				selectedAccount,
				selectedContainer,
			} = this.state;

			const queryArgs = {
				accountId: selectedAccount,
			};

			let errorCode = false;
			let errorMsg = '';

			let responseData = await data.get( 'modules', 'tagmanager', 'list-accounts', queryArgs, false );

			// Verify if user has access to the selected account.
			if ( selectedAccount ) {
				const hasAccessToAccount = responseData.accounts.filter( account => {
					return account.accountId === selectedAccount;
				} );

				if ( 0 === hasAccessToAccount.length ) {
					errorCode = 'insufficientPermissions';
					errorMsg  = __( 'You currently don\'t have access to this Google Tag Manager account. You can either request access from your team, or remove this Google Tag Manager snippet and connect to a different account.', 'google-site-kit' );
				}
				if ( this._isMounted ) {
					this.setState( {
						isLoading: false,
						accounts: responseData.accounts,
						refetch: false,
						errorCode,
						errorMsg,
					} );
				}
			} else {

				const chooseContainer = {
					containerId: 0,
					publicId: 0
				};
				responseData.containers.push( chooseContainer );

				if ( this._isMounted ) {
					this.setState( {
						isLoading: false,
						accounts: responseData.accounts,
						selectedAccount: ( selectedAccount ) ? selectedAccount : responseData.accounts[0].accountId,
						containers: responseData.containers,
						selectedContainer: ( selectedContainer ) ? selectedContainer : responseData.containers[0].publicId,
						refetch: false,
					} );
				}
			}
		} catch ( err ) {
			if ( this._isMounted ) {
				this.setState( {
					isLoading: false,
					errorCode: err.code,
					errorMsg: err.message,
					refetch: false,
				} );
			}
		}
	}

	/**
	 * Request Tag Manager accounts.
	 *
	 * @param {string} selectedAccount The account ID to get containers from.
	 */
	async requestTagManagerContainers( selectedAccount ) {
		try {
			const queryArgs = {
				accountId: selectedAccount,
			};

			let responseData = await data.get( 'modules', 'tagmanager', 'list-containers', queryArgs );

			const chooseContainer = {
				containerId: 0,
				publicId: 0
			};
			responseData.containers.push( chooseContainer );
			if ( this._isMounted ) {
				this.setState( {
					containersLoading: false,
					containers: responseData.containers,
					selectedContainer: responseData.containers[0].publicId,
					errorCode: false,
				} );
			}
		} catch ( err ) {
			if ( this._isMounted ) {
				this.setState( {
					errorCode: err.code,
					errorMsg: err.message,
				} );
			}
		}
	}

	async handleSubmit() {
		const {
			selectedAccount,
			selectedContainer
		} = this.state;

		const { finishSetup } = this.props;

		try {
			const optionData = {
				accountId: selectedAccount,
				containerId: selectedContainer,
			};

			return await data.set( 'modules', 'tagmanager', 'save', optionData )
				.then( ( responseData ) => {

					if ( finishSetup ) {
						finishSetup();
					}

					googlesitekit.modules.tagmanager.settings = {
						accountId: responseData.accountId,
						containerId: responseData.containerId,
					};
					if ( this._isMounted ) {
						this.setState( {
							isSaving: false
						} );
					}
				} );
		} catch ( err ) {
			if ( this._isMounted ) {
				this.setState( {
					isLoading: false,
					errorCode: true,
					errorMsg: err.message
				} );
			}
		}
	}

	static createNewAccount( e ) {
		e.preventDefault();
		window.open( 'https://marketingplatform.google.com/about/tag-manager/', '_blank' );
	}

	handleAccountChange( index, item ) {
		const { selectedAccount } = this.state;
		const selectValue = item.getAttribute( 'data-value' );

		if ( selectValue === selectedAccount ) {
			return;
		}

		if ( this._isMounted ) {
			this.setState( {
				containersLoading: true,
				selectedAccount: selectValue,
			} );
		}

		this.requestTagManagerContainers( selectValue );
	}

	handleContainerChange( index, item ) {
		const { selectedContainer } = this.state;
		const selectValue = item.getAttribute( 'data-value' );

		if ( selectValue === selectedContainer ) {
			return;
		}

		if ( this._isMounted ) {
			this.setState( {
				selectedContainer: selectValue,
			} );
		}

	}

	refetchAccount( e ) {
		e.preventDefault();
		if ( this._isMounted ) {
			this.setState( {
				isLoading: true,
				refetch: true,
				errorCode: false,
			} );
		}
	}

	renderSettingsInfo() {
		const {
			selectedAccount,
			selectedContainer,
		} = this.state;

		return (
			<Fragment>

							{ __( 'Account', 'google-site-kit' ) }

							{ selectedAccount || false }

							{ __( 'Container ID', 'google-site-kit' ) }

							{ selectedContainer || false }

			</Fragment>
		);
	}

	renderAccountDropdownForm() {
		const {
			accounts,
			selectedAccount,
			containers,
			selectedContainer,
			isLoading,
			containersLoading,
		} = this.state;

		const {
			onSettingsPage,
		} = this.props;

		if ( isLoading ) {
			return <ProgressBar/>;
		}

		if ( 0 >= accounts.length ) {
			return (
				<Fragment>

						<Button onClick={ TagmanagerSetup.createNewAccount }>{ __( 'Create an account', 'google-site-kit' ) }</Button>

							<Link onClick={ this.refetchAccount }>{ __( 'Re-fetch My Account', 'google-site-kit' ) }</Link>

				</Fragment>
			);
		}

		return (
			<Fragment>

{ __( 'Please select your Tag Manager account and container below, the snippet will be inserted automatically into your site.', 'google-site-kit' ) }

					<Select
						enhanced
						name='accounts'
						label={ __( 'Account', 'google-site-kit' ) }
						value={ selectedAccount }
						onEnhancedChange={ this.handleAccountChange }
						outlined
					>
						{ accounts.map( account =>
							<Option
								key={ account.accountId }
								value={ account.accountId }>
								{ account.name }
							</Option> ) }
					</Select>

					{ containersLoading ? ( <ProgressBar small /> ) : (
						<Select
							enhanced
							name='containers'
							label={ __( 'Container', 'google-site-kit' ) }
							value={ selectedContainer }
							onEnhancedChange={ this.handleContainerChange }
							outlined
						>
							{ containers.map( container =>
								<Option
									key={ container.containerId }
									value={ container.publicId }>
									{
										0 === container.publicId ?
											__( 'Set up a new container', 'google-site-kit' ) :
											container.publicId
									}
								</Option> ) }
						</Select>
					) }

				{ /*Render the continue and skip button.*/ }
				{
					! onSettingsPage &&

						<Button onClick={ this.handleSubmit }>{ __( 'Confirm & Continue', 'google-site-kit' ) }</Button>

				}

			</Fragment>
		);
	}

	/**
	 * Render Error or Notice format depending on the errorCode.
	 */
	renderErrorOrNotice() {
		const {
			errorCode,
			errorMsg,
		} = this.state;

		const {
			onSettingsPage,
		} = this.props;

		if ( ! errorCode ) {
			return null;
		}

		let showError = true; // default error message.
		let showNotice = false;
		let message = errorMsg;

		switch ( true ) {
				case onSettingsPage && 'insufficientPermissions' === errorCode:
					showError = false;
					showNotice = true;
					break;
		}

		if ( showError && 0 < message.length ) {
			return (

{ __( 'Error:', 'google-site-kit' ) } { message }

			);
		}

		if ( showNotice && 0 < message.length ) {
			return (

{ message }

			);
		}
	}

	render() {
		const {
			onSettingsPage,
			isEditing
		} = this.props;

		return (

				{
					! onSettingsPage &&
					<Fragment>

							<SvgIcon id="tagmanager" width="33" height="33"/>

							{ __( 'Tag Manager', 'google-site-kit' ) }

					</Fragment>
				}

				{ this.renderErrorOrNotice() }

				{ isEditing && this.renderAccountDropdownForm() }

				{ ! isEditing && this.renderSettingsInfo() }

		);
	}
}

TagmanagerSetup.propTypes = {
	onSettingsPage: PropTypes.bool,
	finishSetup: PropTypes.func,
	isEditing: PropTypes.bool,
};

TagmanagerSetup.defaultProps = {
	onSettingsPage: true,
	isEditing: false,
};

export default TagmanagerSetup;
<commit_msg>add loading bar while fetching data for settings info<commit_after>/**
 * TagmanagerSetup component.
 *
 * Site Kit by Google, Copyright 2019 Google LLC
 *
 * 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
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import Button from 'GoogleComponents/button';
import Link from 'GoogleComponents/link';
import data from 'GoogleComponents/data';
import ProgressBar from 'GoogleComponents/progress-bar';
import { Select, Option } from 'SiteKitCore/material-components';
import SvgIcon from 'GoogleUtil/svg-icon';
import PropTypes from 'prop-types';
import { toggleConfirmModuleSettings } from 'GoogleUtil';

const { __ } = wp.i18n;
const { Component, Fragment } = wp.element;
const {
	removeFilter,
	addFilter,
} = wp.hooks;

class TagmanagerSetup extends Component {

	constructor( props ) {
		super( props );

		const {
			accountId,
			containerId,
		} = googlesitekit.modules.tagmanager.settings;

		this.state = {
			isLoading: true,
			accounts: [],
			containers: [],
			errorCode: false,
			errorMsg: '',
			refetch: false,
			selectedAccount: accountId ? accountId : 0,
			selectedContainer: containerId ? containerId : 0,
			containersLoading: false,
		};

		this.handleSubmit = this.handleSubmit.bind( this );
		this.renderAccountDropdownForm = this.renderAccountDropdownForm.bind( this );
		this.handleAccountChange = this.handleAccountChange.bind( this );
		this.handleContainerChange = this.handleContainerChange.bind( this );
		this.refetchAccount = this.refetchAccount.bind( this );

	}

	componentDidMount() {
		this._isMounted = true;
		this.requestTagManagerAccounts();

		// Handle save hook from the settings page.
		addFilter( 'googlekit.SettingsConfirmed',
			'googlekit.TagmanagerSettingsConfirmed',
			( chain, module ) => {
				if ( 'tagmanager' !== module.replace( '-module', '' ) ) {
					return chain;
				}
				const { isEditing } = this.props;
				if ( isEditing ) {
					return this.handleSubmit();
				}
			} );

		this.toggleConfirmChangesButton();
	}

	componentDidUpdate() {
		const { refetch } = this.state;

		if ( refetch ) {
			this.requestTagManagerAccounts();
		}

		this.toggleConfirmChangesButton();
	}

	componentWillUnmount() {
		this._isMounted = false;

		removeFilter( 'googlekit.SettingsConfirmed', 'googlekit.TagmanagerSettingsConfirmed' );
	}

	/**
	 * Toggle confirm changes button disable/enable depending on the changed settings.
	 */
	toggleConfirmChangesButton() {

		if ( ! this.props.isEditing ) {
			return;
		}

		const settingsMapping = {
			selectedContainer: 'containerId',
			selectedAccount: 'selectedAccount',
		};

		toggleConfirmModuleSettings( 'tagmanager', settingsMapping, this.state );
	}

	/**
	 * Request Tag Manager accounts.
	 */
	async requestTagManagerAccounts() {

		try {
			const {
				selectedAccount,
				selectedContainer,
			} = this.state;

			const queryArgs = {
				accountId: selectedAccount,
			};

			let errorCode = false;
			let errorMsg = '';

			let responseData = await data.get( 'modules', 'tagmanager', 'list-accounts', queryArgs, false );

			// Verify if user has access to the selected account.
			if ( selectedAccount ) {
				const hasAccessToAccount = responseData.accounts.filter( account => {
					return account.accountId === selectedAccount;
				} );

				if ( 0 === hasAccessToAccount.length ) {
					data.deleteCache( 'tagmanager', 'list-accounts' );

					errorCode = 'insufficientPermissions';
					errorMsg  = __( 'You currently don\'t have access to this Google Tag Manager account. You can either request access from your team, or remove this Google Tag Manager snippet and connect to a different account.', 'google-site-kit' );
				}
			}

			const chooseContainer = {
				containerId: 0,
				publicId: 0
			};
			responseData.containers.push( chooseContainer );

			if ( this._isMounted ) {
				this.setState( {
					isLoading: false,
					accounts: responseData.accounts,
					selectedAccount: ( selectedAccount ) ? selectedAccount : responseData.accounts[0].accountId,
					containers: responseData.containers,
					selectedContainer: ( selectedContainer ) ? selectedContainer : responseData.containers[0].publicId,
					refetch: false,
					errorCode,
					errorMsg,
				} );
			}
		} catch ( err ) {
			if ( this._isMounted ) {
				this.setState( {
					isLoading: false,
					errorCode: err.code,
					errorMsg: err.message,
					refetch: false,
				} );
			}
		}
	}

	/**
	 * Request Tag Manager accounts.
	 *
	 * @param {string} selectedAccount The account ID to get containers from.
	 */
	async requestTagManagerContainers( selectedAccount ) {
		try {
			const queryArgs = {
				accountId: selectedAccount,
			};

			let responseData = await data.get( 'modules', 'tagmanager', 'list-containers', queryArgs );

			const chooseContainer = {
				containerId: 0,
				publicId: 0
			};
			responseData.containers.push( chooseContainer );
			if ( this._isMounted ) {
				this.setState( {
					containersLoading: false,
					containers: responseData.containers,
					selectedContainer: responseData.containers[0].publicId,
					errorCode: false,
				} );
			}
		} catch ( err ) {
			if ( this._isMounted ) {
				this.setState( {
					errorCode: err.code,
					errorMsg: err.message,
				} );
			}
		}
	}

	async handleSubmit() {
		const {
			selectedAccount,
			selectedContainer
		} = this.state;

		const { finishSetup } = this.props;

		try {
			const optionData = {
				accountId: selectedAccount,
				containerId: selectedContainer,
			};

			return await data.set( 'modules', 'tagmanager', 'save', optionData )
				.then( ( responseData ) => {

					if ( finishSetup ) {
						finishSetup();
					}

					googlesitekit.modules.tagmanager.settings = {
						accountId: responseData.accountId,
						containerId: responseData.containerId,
					};
					if ( this._isMounted ) {
						this.setState( {
							isSaving: false
						} );
					}
				} );
		} catch ( err ) {
			if ( this._isMounted ) {
				this.setState( {
					isLoading: false,
					errorCode: true,
					errorMsg: err.message
				} );
			}
		}
	}

	static createNewAccount( e ) {
		e.preventDefault();
		window.open( 'https://marketingplatform.google.com/about/tag-manager/', '_blank' );
	}

	handleAccountChange( index, item ) {
		const { selectedAccount } = this.state;
		const selectValue = item.getAttribute( 'data-value' );

		if ( selectValue === selectedAccount ) {
			return;
		}

		if ( this._isMounted ) {
			this.setState( {
				containersLoading: true,
				selectedAccount: selectValue,
			} );
		}

		this.requestTagManagerContainers( selectValue );
	}

	handleContainerChange( index, item ) {
		const { selectedContainer } = this.state;
		const selectValue = item.getAttribute( 'data-value' );

		if ( selectValue === selectedContainer ) {
			return;
		}

		if ( this._isMounted ) {
			this.setState( {
				selectedContainer: selectValue,
			} );
		}

	}

	refetchAccount( e ) {
		e.preventDefault();
		if ( this._isMounted ) {
			this.setState( {
				isLoading: true,
				refetch: true,
				errorCode: false,
			} );
		}
	}

	renderSettingsInfo() {
		const {
			isLoading,
			selectedAccount,
			selectedContainer,
		} = this.state;

		if ( isLoading ) {
			return <ProgressBar/>;
		}

		return (
			<Fragment>

							{ __( 'Account', 'google-site-kit' ) }

							{ selectedAccount || false }

							{ __( 'Container ID', 'google-site-kit' ) }

							{ selectedContainer || false }

			</Fragment>
		);
	}

	renderAccountDropdownForm() {
		const {
			accounts,
			selectedAccount,
			containers,
			selectedContainer,
			isLoading,
			containersLoading,
		} = this.state;

		const {
			onSettingsPage,
		} = this.props;

		if ( isLoading ) {
			return <ProgressBar/>;
		}

		if ( 0 >= accounts.length ) {
			return (
				<Fragment>

						<Button onClick={ TagmanagerSetup.createNewAccount }>{ __( 'Create an account', 'google-site-kit' ) }</Button>

							<Link onClick={ this.refetchAccount }>{ __( 'Re-fetch My Account', 'google-site-kit' ) }</Link>

				</Fragment>
			);
		}

		return (
			<Fragment>

{ __( 'Please select your Tag Manager account and container below, the snippet will be inserted automatically into your site.', 'google-site-kit' ) }

					<Select
						enhanced
						name='accounts'
						label={ __( 'Account', 'google-site-kit' ) }
						value={ selectedAccount }
						onEnhancedChange={ this.handleAccountChange }
						outlined
					>
						{ accounts.map( account =>
							<Option
								key={ account.accountId }
								value={ account.accountId }>
								{ account.name }
							</Option> ) }
					</Select>

					{ containersLoading ? ( <ProgressBar small /> ) : (
						<Select
							enhanced
							name='containers'
							label={ __( 'Container', 'google-site-kit' ) }
							value={ selectedContainer }
							onEnhancedChange={ this.handleContainerChange }
							outlined
						>
							{ containers.map( container =>
								<Option
									key={ container.containerId }
									value={ container.publicId }>
									{
										0 === container.publicId ?
											__( 'Set up a new container', 'google-site-kit' ) :
											container.publicId
									}
								</Option> ) }
						</Select>
					) }

				{ /*Render the continue and skip button.*/ }
				{
					! onSettingsPage &&

						<Button onClick={ this.handleSubmit }>{ __( 'Confirm & Continue', 'google-site-kit' ) }</Button>

				}

			</Fragment>
		);
	}

	/**
	 * Render Error or Notice format depending on the errorCode.
	 */
	renderErrorOrNotice() {
		const {
			errorCode,
			errorMsg,
		} = this.state;

		const {
			onSettingsPage,
		} = this.props;

		if ( ! errorCode ) {
			return null;
		}

		let showError = true; // default error message.
		let showNotice = false;
		let message = errorMsg;

		switch ( true ) {
				case onSettingsPage && 'insufficientPermissions' === errorCode:
					showError = false;
					showNotice = true;
					break;
		}

		if ( showError && 0 < message.length ) {
			return (

{ __( 'Error:', 'google-site-kit' ) } { message }

			);
		}

		if ( showNotice && 0 < message.length ) {
			return (

{ message }

			);
		}
	}

	render() {
		const {
			onSettingsPage,
			isEditing
		} = this.props;

		return (

				{
					! onSettingsPage &&
					<Fragment>

							<SvgIcon id="tagmanager" width="33" height="33"/>

							{ __( 'Tag Manager', 'google-site-kit' ) }

					</Fragment>
				}

				{ this.renderErrorOrNotice() }

				{ isEditing && this.renderAccountDropdownForm() }

				{ ! isEditing && this.renderSettingsInfo() }

		);
	}
}

TagmanagerSetup.propTypes = {
	onSettingsPage: PropTypes.bool,
	finishSetup: PropTypes.func,
	isEditing: PropTypes.bool,
};

TagmanagerSetup.defaultProps = {
	onSettingsPage: true,
	isEditing: false,
};

export default TagmanagerSetup;
<|endoftext|>


<commit_before>/***
 * Copyright (c) 2015, 2016 Alex Grant (@localnerve), LocalNerve LLC
 * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
 *
 * Handling for background image requests.
 * NOTE: Not really idempotent, because toolbox router Map will have old routes in it.
 * TODO: Handle that little issue.
 */
/* global Promise, Request, caches */
'use strict';

var toolbox = require('sw-toolbox');
var urlm = require('../../../../utils/urls');
var debug = require('../utils/debug')('init.backgrounds');

/**
 * Escape a string for usage in a regular expression.
 */
function regexEscape(s) {
  return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

/**
 * Precache a request if the response does NOT exist.
 *
 * @param {Object} options - options to accompany the cache operation.
 * @param {Object} req - The Request object to cache.
 * @param {Object} res - The Response object, if null, Request should be cached.
 */
function precacheBackground (options, req, res) {
  if (!res) {
    debug(options, 'precaching background', req);
    return toolbox.cache(req.clone(), options);
  }
  return Promise.resolve();
}

/**
 * A precaching read-thru cache
 * When a background request comes in, checks against the other backgrounds.
 * If one or more of the other backgrounds is not in the cache, fetch and cache
 * them right now.
 * Serve the current background request, updating the cache as you go.
 *
 * @param {Object} backgroundUrls - The backgroundUrls used to init the BackgroundStore.
 * @param {Object} request - A Request object
 * @param {Object} values - Ignored, passed on to the strategy.
 * @param {Object} options - The router options.
 */
function precacheBackgrounds (backgroundUrls, request, values, options) {
  // precache/prefetch backgrounds that will be needed next that are not already cached.
  // NOTE: This is an async side-effect - we don't wait for (or handle) the result.
  Promise.all(Object.keys(backgroundUrls).map(function (key) {
    var background = urlm.getLastPathSegment(backgroundUrls[key]),
        reCurrent, notCurrent, reqNotCurrent,
        current = urlm.getLastPathSegment(request.url);

    if (current && background && current !== background) {
      // build the request for the next background
      reCurrent = new RegExp('(' + regexEscape(current) + ')(\/)?$');
      notCurrent = request.url.replace(reCurrent, background + '$2');
      reqNotCurrent = new Request(notCurrent, {
        mode: 'no-cors' // these are from a cdn
      });

      // if reqNotCurrent not in cache, a falsy response will be given to
      // precacheBackground.
      return caches.open(toolbox.options.cache.name).then(function (cache) {
        return cache.match(reqNotCurrent).then(
          precacheBackground.bind(this, options, reqNotCurrent)
        );
      });
    }
  })).catch(function (error) {
    debug('Error prefetching next backgrounds:', error);
  });

  return toolbox.fastest(request, values, options);
}

/**
 * Cache background images for the app
 *
 * @param {Object} payload - The stores payload object.
 * @param {Object} payload.BackgroundStore - The dehydrated BackgroundStore.
 * @param {Object} payload.BackgroundStore.backgroundUrls - The background urls
 * object, containing name-values for backgrounds at imageServiceUrl.
 * @param {String} payload.BackgroundStore.imageServiceUrl - The url to the
 * image service.
 */
module.exports = function backgroundHandler (payload) {
  var backgroundStore = payload.BackgroundStore;

  debug('install background image handler', backgroundStore);

  // Install a precaching, read-thru cache on all requests to the background image service
  toolbox.router.get('*',
    precacheBackgrounds.bind(this, backgroundStore.backgroundUrls), {
    debug: toolbox.options.debug,
    origin: urlm.getHostname(backgroundStore.imageServiceUrl)
  });

  // Nothing deferred (yet), so return a resolved Promise
  return Promise.resolve();
};
<commit_msg>[ci skip] update comments<commit_after>/***
 * Copyright (c) 2015, 2016 Alex Grant (@localnerve), LocalNerve LLC
 * Copyrights licensed under the BSD License. See the accompanying LICENSE file for terms.
 *
 * Handling for background image requests.
 */
/* global Promise, Request, caches */
'use strict';

var toolbox = require('sw-toolbox');
var urlm = require('../../../../utils/urls');
var debug = require('../utils/debug')('init.backgrounds');

/**
 * Escape a string for usage in a regular expression.
 */
function regexEscape(s) {
  return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

/**
 * Precache a request if the response does NOT exist.
 *
 * @param {Object} options - options to accompany the cache operation.
 * @param {Object} req - The Request object to cache.
 * @param {Object} res - The Response object, if null, Request should be cached.
 */
function precacheBackground (options, req, res) {
  if (!res) {
    debug(options, 'precaching background', req);
    return toolbox.cache(req.clone(), options);
  }
  return Promise.resolve();
}

/**
 * A precaching read-thru cache
 * When a background request comes in, checks against the other backgrounds.
 * If one or more of the other backgrounds is not in the cache, fetch and cache
 * them right now.
 * Serve the current background request, updating the cache as you go.
 *
 * @param {Object} backgroundUrls - The backgroundUrls used to init the BackgroundStore.
 * @param {Object} request - A Request object
 * @param {Object} values - Ignored, passed on to the strategy.
 * @param {Object} options - The router options.
 */
function precacheBackgrounds (backgroundUrls, request, values, options) {
  // precache/prefetch backgrounds that will be needed next that are not already cached.
  // NOTE: This is an async side-effect - we don't wait for (or handle) the result.
  Promise.all(Object.keys(backgroundUrls).map(function (key) {
    var background = urlm.getLastPathSegment(backgroundUrls[key]),
        reCurrent, notCurrent, reqNotCurrent,
        current = urlm.getLastPathSegment(request.url);

    if (current && background && current !== background) {
      // build the request for the next background
      reCurrent = new RegExp('(' + regexEscape(current) + ')(\/)?$');
      notCurrent = request.url.replace(reCurrent, background + '$2');
      reqNotCurrent = new Request(notCurrent, {
        mode: 'no-cors' // these are from a cdn
      });

      // if reqNotCurrent not in cache, a falsy response will be given to
      // precacheBackground.
      return caches.open(toolbox.options.cache.name).then(function (cache) {
        return cache.match(reqNotCurrent).then(
          precacheBackground.bind(this, options, reqNotCurrent)
        );
      });
    }
  })).catch(function (error) {
    debug('Error prefetching next backgrounds:', error);
  });

  return toolbox.fastest(request, values, options);
}

/**
 * Cache background images for the app
 *
 * @param {Object} payload - The stores payload object.
 * @param {Object} payload.BackgroundStore - The dehydrated BackgroundStore.
 * @param {Object} payload.BackgroundStore.backgroundUrls - The background urls
 * object, containing name-values for backgrounds at imageServiceUrl.
 * @param {String} payload.BackgroundStore.imageServiceUrl - The url to the
 * image service.
 */
module.exports = function backgroundHandler (payload) {
  var backgroundStore = payload.BackgroundStore;

  debug('install background image handler', backgroundStore);

  // Install a precaching, read-thru cache on all requests to the background image service
  toolbox.router.get('*',
    precacheBackgrounds.bind(this, backgroundStore.backgroundUrls), {
    debug: toolbox.options.debug,
    origin: urlm.getHostname(backgroundStore.imageServiceUrl)
  });

  // Nothing deferred (yet), so return a resolved Promise
  return Promise.resolve();
};
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import { DATA_LAYER } from './constants';
import { config, gtag } from './shared';

/**
 * Loads the Analytics tracking script.
 *
 * @param {boolean} [sendPageView=true] Whether to send a page view event or not upon loading.
 * @return {Promise<void>} Promise.
 */
function loadTrackingScript(sendPageView = true) {
  const SCRIPT_IDENTIFIER = 'data-web-stories-tracking';

  if (document.querySelector(`script[${SCRIPT_IDENTIFIER}]`)) {
    return Promise.resolve();
  }

  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.setAttribute(SCRIPT_IDENTIFIER, '');
    script.async = true;
    script.src = `https://www.googletagmanager.com/gtag/js?id=${config.trackingId}&l=${DATA_LAYER}`;
    script.addEventListener('load', resolve);
    script.addEventListener('error', reject);
    document.head.appendChild(script);

    gtag('js', new Date());
    // TODO: provide custom pageview-related parameters?
    // See https://developers.google.com/analytics/devguides/collection/gtagjs/pages
    gtag('config', config.trackingId, {
      app_name: config.appName,
      send_page_view: sendPageView,
    });
  });
}

async function enableTracking(sendPageView) {
  if (!config.trackingAllowed) {
    return Promise.resolve();
  }

  config.trackingEnabled = true;

  //eslint-disable-next-line no-return-await
  return await loadTrackingScript(sendPageView);
}

export default enableTracking;
<commit_msg>Telemetry: anonymize IPs<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import { DATA_LAYER } from './constants';
import { config, gtag } from './shared';

/**
 * Loads the Analytics tracking script.
 *
 * @param {boolean} [sendPageView=true] Whether to send a page view event or not upon loading.
 * @return {Promise<void>} Promise.
 */
function loadTrackingScript(sendPageView = true) {
  const SCRIPT_IDENTIFIER = 'data-web-stories-tracking';

  if (document.querySelector(`script[${SCRIPT_IDENTIFIER}]`)) {
    return Promise.resolve();
  }

  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.setAttribute(SCRIPT_IDENTIFIER, '');
    script.async = true;
    script.src = `https://www.googletagmanager.com/gtag/js?id=${config.trackingId}&l=${DATA_LAYER}`;
    script.addEventListener('load', resolve);
    script.addEventListener('error', reject);
    document.head.appendChild(script);

    gtag('js', new Date());
    // TODO: provide custom pageview-related parameters?
    // See https://developers.google.com/analytics/devguides/collection/gtagjs/pages
    gtag('config', config.trackingId, {
      anonymize_ip: true,
      app_name: config.appName,
      send_page_view: sendPageView,
    });
  });
}

async function enableTracking(sendPageView) {
  if (!config.trackingAllowed) {
    return Promise.resolve();
  }

  config.trackingEnabled = true;

  //eslint-disable-next-line no-return-await
  return await loadTrackingScript(sendPageView);
}

export default enableTracking;
<|endoftext|>


<commit_before>'use strict';

var http = require('http');
var https = require('https');
var AWS = require('aws-sdk');
var dynamodb = null;

var options = require('./options');
var defaultMusicService = ((options.defaultMusicService != undefined) && (options.defaultMusicService > ''))?options.defaultMusicService:'presets';
var defaultRoom = (options.defaultRoom != undefined)?options.defaultRoom:'';

var AlexaSkill = require('./AlexaSkill');
var EchoSonos = function () {
    AlexaSkill.call(this, options.appid);
};

var STATE_RESPONSES = [
    "This is $currentTitle by $currentArtist",
    "We're listening to $currentTitle by $currentArtist",
    "$currentTitle by $currentArtist"
];

EchoSonos.prototype = Object.create(AlexaSkill.prototype);
EchoSonos.prototype.constructor = EchoSonos;

EchoSonos.prototype.intentHandlers = {

    AlbumIntent: function (intent, session, response) {
        console.log("AlbumIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
        	musicHandler(room, service, '/album/', intent.slots.Name.value, response);
        });
    },

    MusicIntent: function (intent, session, response) {
        console.log("MusicIntent received for room " + intent.slots.Room.value);
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        musicHandler(room, service, '/song/', intent.slots.Name.value, response);
        });
    },

    MusicRadioIntent: function (intent, session, response) {
        console.log("MusicRadioIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        musicHandler(room, service, '/station/', intent.slots.Name.value, response);
        });
    },

    PlayMoreByArtistIntent: function (intent, session, response) {
        console.log("PlayMoreByArtist received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        moreMusicHandler(room, service, '/song/', response);
        });
    },

    PlayMoreLikeTrackIntent: function (intent, session, response) {
        console.log("PlayMoreLikeTrackIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        moreMusicHandler(room, service, '/station/', response);
        });
    },

    SiriusXMStationIntent: function (intent, session, response) {
        console.log("SiriusXMStationIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        siriusXMHandler(room, intent.slots.Station.value, 'station', response);
	    });
    },

    SiriusXMChannelIntent: function (intent, session, response) {
        console.log("SiriusXMChannelIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        siriusXMHandler(room, intent.slots.Channel.value, 'channel', response);
	    });
    },

    PandoraMusicIntent: function (intent, session, response) {
        console.log("PandoraMusicIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        pandoraHandler(room, '/play/', intent.slots.Name.value, response);
	    });
    },

    PandoraThumbsUpIntent: function (intent, session, response) {
        console.log("PandoraThumbsUpIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        pandoraHandler(room, '/thumbsup', '', response);
	    });
    },

    PandoraThumbsDownIntent: function (intent, session, response) {
        console.log("PandoraThumbsDownIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        pandoraHandler(room, '/thumbsdown', '', response);
	    });
    },

    PlayPresetIntent: function (intent, session, response) {
        console.log("PlayPresetIntent received");
        options.path = '/preset/' + encodeURIComponent(intent.slots.Preset.value.toLowerCase());
        httpreq(options, function(error) {
            genericResponse(error, response);
        });
    },

    PlaylistIntent: function (intent, session, response) {
        console.log("PlaylistIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        playlistHandler(room, intent.slots.Preset.value, 'playlist', response);
        });
    },

    FavoriteIntent: function (intent, session, response) {
        console.log("FavoriteIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        playlistHandler(room, intent.slots.Preset.value, 'favorite', response);
        });
    },

    ChangeRoomIntent: function (intent, session, response) {
        console.log("ChangeRoomIntent received");
        if (!options.advancedMode) {
           	response.tell("This command does not work unless advanced mode is turned on");
        } else {
    		changeCurrent('DefaultEcho', intent.slots.Room.value, '', function() {
        		genericResponse('', response);
        	});
        }
    },

    ChangeServiceIntent: function (intent, session, response) {
        console.log("ChangeSERVICEIntent received");
        if (!options.advancedMode) {
           	response.tell("This command does not work unless advanced mode is turned on");
        } else {
	        changeCurrent('DefaultEcho', '', intent.slots.Service.value, function() {
    	    	genericResponse('', response);
        	});
        }
    },

    ChangeRoomAndServiceIntent: function (intent, session, response) {
        console.log("ChangeRoomAndServiceIntent received");
        if (!options.advancedMode) {
           	response.tell("This command does not work unless advanced mode is turned on");
        } else {
	        changeCurrent('DefaultEcho', intent.slots.Room.value, intent.slots.Service.value, function() {
    	    	genericResponse('', response);
        	});
        }
    },

    ResumeAllIntent: function (intent, session, response) {
        console.log("ResumeAllIntent received");
        options.path = '/resumeAll';
        httpreq(options, function(error) {
            genericResponse(error, response);
        });
    },

    ResumeIntent: function (intent, session, response) {
        console.log("ResumeIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        options.path = '/' + encodeURIComponent(room) + '/play';
    	    httpreq(options, function(error) {
        	    genericResponse(error, response);
       	 	});
    	});
    },

    PauseAllIntent: function (intent, session, response) {
        console.log("PauseAllIntent received");
        options.path = '/pauseAll';
        httpreq(options, function(error) {
            genericResponse(error, response);
        });
    },

    PauseIntent: function (intent, session, response) {
        console.log("PauseIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        options.path = '/' + encodeURIComponent(room) + '/pause';
    	    httpreq(options, function(error) {
        	    genericResponse(error, response);
        	});
        });
    },

    VolumeDownIntent: function (intent, session, response) {
        console.log("VolumeDownIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        volumeHandler(room, response, '-10');
        });
    },

    VolumeUpIntent: function (intent, session, response) {
        console.log("VolumeUpIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        volumeHandler(room, response, '+10');
        });
    },

    SetVolumeIntent: function (intent, session, response) {
        console.log("SetVolumeIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        volumeHandler(room, response, intent.slots.Percent.value);
        });
    },

    NextTrackIntent: function (intent, session, response) {
        console.log("NextTrackIntent received");

        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        actOnCoordinator(options, '/next', room,  function (error, responseBodyJson) {
    	        genericResponse(error, response);
        	});
        });
    },

    PreviousTrackIntent: function (intent, session, response) {
        console.log("PreviousTrackIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        actOnCoordinator(options, '/previous', room,  function (error, responseBodyJson) {
    	        genericResponse(error, response);
        	});
        });
    },

    WhatsPlayingIntent: function (intent, session, response) {
        console.log("WhatsPlayingIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        options.path = '/' + encodeURIComponent(room) + '/state';

    	    httpreq(options, function (error, responseJson) {
	            if (!error) {
   		            responseJson = JSON.parse(responseJson);
   	 		        var randResponse = Math.floor(Math.random() * STATE_RESPONSES.length);
            	    var responseText = STATE_RESPONSES[randResponse].replace("$currentTitle", responseJson.currentTrack.title).replace("$currentArtist", responseJson.currentTrack.artist);
                	response.tell(responseText);
            	}
            	else {
                	response.tell(error.message);
            	}
        	});
        });
    },

    MuteIntent: function (intent, session, response) {
        console.log("MuteIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        options.path = '/' + encodeURIComponent(room) + '/mute';
    	    httpreq(options, function(error) {
        	    genericResponse(error, response);
        	});
        });
    },

    UnmuteIntent: function (intent, session, response) {
        console.log("UnmuteIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        options.path = '/' + encodeURIComponent(room) + '/unmute';
    	    httpreq(options, function(error) {
        	    genericResponse(error, response);
        	});
        });
    },

    ClearQueueIntent: function (intent, session, response) {
        console.log("ClearQueueIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        actOnCoordinator(options, '/clearqueue', room,  function (error, responseBodyJson) {
    	        genericResponse(error, response);
        	});
        });
    },

    RepeatIntent: function (intent, session, response) {
        console.log("RepeatIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
        	toggleHandler(room, intent.slots.Toggle.value, "repeat", response);
        });
    },

    ShuffleIntent: function (intent, session, response) {
        console.log("ShuffleIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        toggleHandler(room, intent.slots.Toggle.value, "shuffle", response);
        });
    },

    CrossfadeIntent: function (intent, session, response) {
        console.log("CrossfadeIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        toggleHandler(room, intent.slots.Toggle.value, "crossfade", response);
        });
    },

    UngroupIntent: function (intent, session, response) {
        console.log("UngroupIntent received");
        loadCurrentRoomAndService('DefaultEcho', intent.slots.Room.value, function(room, service) {
	        options.path = '/' + encodeURIComponent(room) + '/isolate';
	        httpreq(options, function(error) {
	            genericResponse(error, response);
	        });
        });
    },

    JoinGroupIntent: function (intent, session, response) {
        console.log("JoinGroupIntent received");
        options.path = '/' + encodeURIComponent(intent.slots.JoiningRoom.value) + '/join/'
                + encodeURIComponent(intent.slots.PlayingRoom.value);
        httpreq(options, function(error) {
            genericResponse(error, response);
        });
    }
}

/** Handles Apple Music, Spotify, Deezer, library, or presets. The default can be specified in options.js or changed if advanced mode is turned on */
function musicHandler(roomValue, service, cmdpath, name, response) {

    if (service == 'presets') {
        options.path = '/preset/' + encodeURIComponent(name);
        httpreq(options, function(error) {
        	genericResponse(error, response);
        });
    } else {
        var skillPath = '/musicsearch/' + service.toLowerCase() + cmdpath + encodeURIComponent(name);
        var msgStart = (cmdpath.startsWith('/station'))?'Started ':'Queued and started ';
        var msgEnd = (cmdpath.startsWith('/station'))?' radio':'';

        actOnCoordinator(options, skillPath, roomValue, function(error, responseBodyJson) {
        	if (error) {
            	response.tell(error.message);
          	} else {
            	response.tell(msgStart + name + msgEnd);
            }
        });
    }
}

/** Handles Apple Music - plays artist tracks or plays a radio station for the current track */
function moreMusicHandler(roomValue, service, cmdpath, response) {
    options.path = '/' + encodeURIComponent(roomValue) + '/state';

    httpreq(options, function (error, responseJson) {
        if (!error) {
            responseJson = JSON.parse(responseJson);
            var name = responseJson.currentTrack.artist;

            if (cmdpath.startsWith('/station') && (['apple','spotify','deezer'].indexOf(service) != -1)) {
                name += ' ' + responseJson.currentTrack.title;
            }

            musicHandler(roomValue, service, '/musicsearch/', cmdpath, name, response);
        } else {
            genericResponse(error, response);
        }
    });
}

/** Handles SiriusXM Radio */
function siriusXMHandler(roomValue, name, type, response) {

	var skillPath = '/siriusxm/' + encodeURIComponent(name.replace(' ','+'));

    actOnCoordinator(options, skillPath, roomValue, function(error, responseBodyJson) {
    	if (error) {
        	genericResponse(error, response);
    	} else {
          	response.tell('Sirius XM ' + type + ' ' + name + ' started');
    	}
    });
}

/** Handles SiriusXM Radio */
function pandoraHandler(roomValue, cmdpath, name, response) {

	var skillPath = '/pandora' + cmdpath + ((cmdpath=='/play/')?encodeURIComponent(name):'');

    actOnCoordinator(options, skillPath, roomValue, function(error, responseBodyJson) {
		if (error) {
	   		response.tell(error.message);
    	} else {
      		if (cmdpath == '/play/') {
         		response.tell('Pandora ' + name + ' started');
      		} else {
        		genericResponse(error, response);
      		}
      	}
    });
}

/** Handles playlists and favorites */
function playlistHandler(roomValue, presetValue, skillName, response) {
    var skillPath = '/' + skillName + '/' + encodeURIComponent(presetValue.toLowerCase());

    // This first action queues up the playlist / favorite, and it shouldn't say anything unless there's an error
    actOnCoordinator(options, skillPath, roomValue, function(error, responseBodyJson) {
        if (error) {
            genericResponse(error, response);
        }
    });

    // The 2nd action actually plays the playlist / favorite
    actOnCoordinator(options, '/play', roomValue, function(error, responseBodyJson) {
        genericResponse(error, response, "Queued and started " + presetValue);
    });
}

/** Handles all skills of the form /roomname/toggle/[on,off] */
function toggleHandler(roomValue, toggleValue, skillName, response) {
    if (!toggleValue || (toggleValue != 'on' && toggleValue != 'off')) {
        response.tell("I need to know if I should turn  " + skillName + " on or off. Example: Alexa, tell Sonos to turn " + skillName + " on");
        return;
    }

    options.path = '/' + encodeURIComponent(roomValue) + '/' + skillName + '/' + toggleValue;

    httpreq(options, function(error) {
        if (!error) {
            response.tell("Turned " + skillName + " " + toggleValue + " in " + roomValue);
        }
        else {
          response.tell(error.message);
        }
    });
}

/** Handles up, down, & absolute volume for either an individual room or an entire group */
function volumeHandler(roomValue, response, volume) {
    var roomAndGroup = parseRoomAndGroup(roomValue);

    if (!roomAndGroup.room) {
        response.tell("Please specify a room. For example, turn the volume down in the KITCHEN");
        return;
    }

    if (!roomAndGroup.group) {
        options.path = '/' + encodeURIComponent(roomAndGroup.room) + '/volume/' + volume;

        httpreq(options, function(error) {
            genericResponse(error, response);
        });
    }

    else {
        actOnCoordinator(options, '/groupVolume/' + volume, roomAndGroup.room,  function (error, responseBodyJson) {
            genericResponse(error, response);
        });
    }
}

/* Given a string roomArgument that either looks like "my room" or "my room group",
 * returns an object with two members:
 *   obj.group: true if roomArgument ends with "group", false otherwise.
 *   obj.room: if roomArgument is "my room group", returns "my room"
 */
function parseRoomAndGroup(roomArgument) {
    var roomAndGroupParsed = new Object();
    roomAndGroupParsed.group = false;
    roomAndGroupParsed.room = false;

    if (!roomArgument) {
        return roomAndGroupParsed;
    }

    var groupIndex = roomArgument.indexOf("group");

    if (groupIndex && (groupIndex + 4 == (roomArgument.length - 1)) && roomArgument.length >= 7) {
        roomAndGroupParsed.group = true;
        roomAndGroupParsed.room = roomArgument.substr(0, groupIndex - 1);
    }
    else {
        roomAndGroupParsed.room = roomArgument;
    }

    return roomAndGroupParsed;
}

function isBlank(val)
{
    return ((val == undefined) || (val == null) || (val == ''));
}

function changeCurrent(echoId, room, service, OnCompleteFun) {
var updateExpression = '';
var values = {};

    if (options.advancedMode) {
		if (!isBlank(room) && !isBlank(service)) {
    		updateExpression = "set currentRoom=:r, currentMusicService=:s";
    		values = {":r":room, "s:":service};
    	} else
    	if (!isBlank(room)) {
    		updateExpression = "set currentRoom=:r";
    		values = {":r":room};
    	} else
    	if (!isBlank(service)) {
    		updateExpression = "set currentMusicService=:s";
    		values = {":s":service};
   		}

		if (updateExpression != '') {
			var docClient = new AWS.DynamoDB.DocumentClient();
			var params = {
    			TableName: "echo-sonos",
    			Key:{
        			"echoid": echoId
    			},
    			UpdateExpression: updateExpression,
    			ExpressionAttributeValues:values,
    			ReturnValues:"UPDATED_NEW"
			};

			console.log("Updating current defaults...");
			docClient.update(params, function(err, data) {
    			if (err) {
        			console.error("Unable to update current defaults. Error JSON:", JSON.stringify(err, null, 2));
    			} else {
        			console.log("Update of current defaults succeeded:", JSON.stringify(data, null, 2));
   				}
				OnCompleteFun();
			});
		}
	} else {
		console.error("Unable to change defaults when not in Advanced Mode");
	}
}

function loadCurrentRoomAndService(echoId, room, OnCompleteFun) {
var service = '';

    function checkDefaults()
    {
    	if (isBlank(room)) {
    		room = defaultRoom;
    	}
    	if (isBlank(service)) {
    		service = defaultMusicService;
    	}
	}

    console.log("Advanced Mode = " + options.advancedMode);
    if (options.advancedMode) {

    	function addCurrent(OnCompleteFun)
    	{
        	checkDefaults();

	    	var docClient = new AWS.DynamoDB.DocumentClient();
			var params = {
    			TableName: "echo-sonos",
    			Item:{
        			"echoid": echoId,
        			"currentRoom": room,
        			"currentMusicService": service
    			}
			};

			console.log("Adding current settings record");
			docClient.put(params, function(err, data) {
				console.log("err=" + JSON.stringify(err, null, 2));
				console.log("data=" + JSON.stringify(data, null, 2));
				if (err) {
					console.error("Unable to add default. Error JSON:", JSON.stringify(err, null, 2));
    			} else {
        			OnCompleteFun(room, service);
   	 			}
			});
    	}

    	function readCurrent(OnCompleteFun)
    	{
	    	var newRoom = '';
	    	var newService = '';
	    	var docClient = new AWS.DynamoDB.DocumentClient();
			var params = {
    				TableName: "echo-sonos",
   					Key:{
        				"echoid": echoId
    				}
				};

			console.log("Reading current settings");
			docClient.get(params, function(err, data) {
				//console.log("err=" + JSON.stringify(err, null, 2));
				//console.log("data=" + JSON.stringify(data, null, 2));
				if (err || (data.Item == undefined)) {
					addCurrent(OnCompleteFun);
    			} else {
    		    	if (isBlank(room)) {
    		    		room = data.Item.currentRoom;
    		    	} else
    		    	if (room != data.Item.currentRoom) {
    		    	  newRoom = room;
    		    	}
    		    	if (isBlank(service)) {
    		    		service = data.Item.currentMusicService;
    		    	} else
    		    	if (service != data.Item.currentMusicService) {
    		    		newService = service;
    		    	}
 					console.log("room=" + room +" newRoom=" + newRoom + "  service=" + service + " newService=" + newService);
    		    	if (isBlank(newRoom) && isBlank(newService)) {
        				OnCompleteFun(room, service);
        			} else {
        				changeCurrent(echoId, newRoom, newService, function() {
	        				OnCompleteFun(room, service);
        				});
        			}
   	 			}
			});
    	}

        AWS.config.update({
            region: process.env.AWS_REGION,
            endpoint: "https://dynamodb." + process.env.AWS_REGION + ".amazonaws.com"
        });
        var dynamodb = new AWS.DynamoDB();

        dynamodb.listTables(function(err, data) {
			if (err) {
                console.log(err, err.stack);
			} else
            if ((data.TableNames.length == 0) || (data.TableNames.indexOf("echo-sonos") == -1))  {
                var params = {
                    TableName : "echo-sonos",
                    KeySchema: [
                        { AttributeName: "echoid", KeyType: "HASH"}  //Partition key
                    ],
                    AttributeDefinitions: [
        				{ AttributeName: "echoid", AttributeType: "S" }
    				],
    				ProvisionedThroughput: {
        				ReadCapacityUnits: 1,
        				WriteCapacityUnits: 1
    				}
				};

				console.log("Create echo-sonos table");
				dynamodb.createTable(params, function(err, data) {
    				if (err) {
        				console.error("Unable to create table. Error JSON:", JSON.stringify(err, null, 2));
    				} else {
    					params = {
                    		TableName : "echo-sonos"
                    	};

						dynamodb.waitFor('tableExists', params, function(err, data) {
  							if (err) {
        						console.error("Unable to wait for table table. Error JSON:", JSON.stringify(err, null, 2));
  							} else {
  								addCurrent(OnCompleteFun);
  							}
						});
    				}
				});
            } else
            if (isBlank(service) || isBlank(room)) {
            	readCurrent(OnCompleteFun);
            }
        });
    } else {
        checkDefaults();
        OnCompleteFun(room, service);
    }
}

function httpreq(options, responseCallback) {
    var transport = options.useHttps ? https : http;

    console.log("Sending " + (options.useHttps ? "HTTPS" : "HTTP" ) + " request to: " + options.path);

    var req = transport.request(options, function(httpResponse) {
        var body = '';

        httpResponse.on('data', function(data) {
            body += data;
        });

        httpResponse.on('end', function() {
            responseCallback(undefined, body);
        });
    });

    req.on('error', function(e) {
        responseCallback(e);
    });

    req.end();
}

// 1) grab /zones and find the coordinator for the room being asked for
// 2) perform an action on that coordinator
function actOnCoordinator(options, actionPath, room, onCompleteFun) {
    options.path = '/zones';
    console.log("getting zones...");

    var handleZonesResponse = function (error, responseJson) {
        if (!error) {
            responseJson = JSON.parse(responseJson);
            var coordinatorRoomName = findCoordinatorForRoom(responseJson, room);

            options.path = '/' + encodeURIComponent(coordinatorRoomName) + actionPath;
            console.log(options.path);
            httpreq(options, onCompleteFun);
        }
        else {
            onCompleteFun(error);
        }
    }

    httpreq(options, handleZonesResponse);
}

function genericResponse(error, response, success) {
    if (!error) {
        if (!success) {
            response.tell("OK");
        }
        else {
            response.tell(success);
        }
    }
    else {
        response.tell("The Lambda service encountered an error: " + error.message);
    }
}

// Given a room name, returns the name of the coordinator for that room
function findCoordinatorForRoom(responseJson, room) {
    console.log("finding coordinator for room: " + room);

    for (var i = 0; i < responseJson.length; i++) {
        var zone = responseJson[i];

        for (var j = 0; j < zone.members.length; j++) {
            var member = zone.members[j];

            if ((member.roomName != undefined) && (member.roomName.toLowerCase() == room.toLowerCase())) {
                return zone.coordinator.roomName;
            }
        }
    }
}

// Create the handler that responds to the Alexa Request.
exports.handler = function (event, context) {
    // Create an instance of the EchoSonos skill.
    var echoSonos = new EchoSonos();
    echoSonos.execute(event, context);
};
<commit_msg>Delete index.js<commit_after><|endoftext|>


<commit_before>dojo.provide("dojox.lang.tests.async");

dojo.require("dojox.lang.async");
dojo.require("dojox.lang.async.timeout");
dojo.require("dojox.lang.async.topic");

(function(){
	var async = dojox.lang.async,
		wait = async.timeout.from,
		QUANT     = 50, // ms
		MAX_TICKS = 5;

	function randomTimes(n){
		var a = [];
		for(var i = 0; i < n; ++i){
			a.push((Math.floor(Math.random() * MAX_TICKS) + 1) * QUANT);
		}
		return a;
	}

	function getMin(a){
		return Math.min.apply(Math, a);
	}

	function getMax(a){
		return Math.max.apply(Math, a);
	}

	function waitAndExpect(expected, ms){
		return function(value){
			console.log("waitAndExpect: ", value, ", expected: ", expected);
			if(expected !== value){
				throw new Error("Unexpected value");
			}
			return wait(ms)();
		}
	}

	tests.register("dojox.lang.tests.async", [
		function smokeTest(){
			var a = randomTimes(1),
				r = new dojo.Deferred();
			wait(a[0])().addCallback(function(x){
				if(r == a[0]){
					throw new Error("smokeTest: wrong result");
				}
				r.callback();
			});
			return r;
		},
		function testSeq(){
			var a = randomTimes(5),
				fs = dojo.map(a, function(ms, i){
					return waitAndExpect(i && a[i - 1], ms);
				});
			return async.seq.apply(async, fs)(0).addCallback(function(value){
				if(a[a.length - 1] !== value){
					throw new Error("testSeq: wrong time");
				}
			});
		},
		function testPar(){
			var a = randomTimes(5),
				fs = dojo.map(a, function(ms){
					return waitAndExpect(0, ms);
				});
			return async.par.apply(async, fs)(0).addCallback(function(value){
				console.log(a, " - ", value);
				if(a !== value){
					throw new Error("testPar: wrong time");
				}
			});
		},
		function testAny(){
			var a = randomTimes(5),
				min = getMin(a),
				fs = dojo.map(a, function(ms){
					return waitAndExpect(0, ms);
				});
			return async.any.apply(async, fs)(0).addCallback(function(value){
				console.log(min, " - ", value);
				if(min !== value){
					throw new Error("testAny: wrong time");
				}
			});
		},
		function testSelect(){

		},
		function testIfThen(){

		},
		function testLoop(){

		}
	]);
})();
<commit_msg>dojox.lang.async: adding unit tests for select(), ifThen(), and loop(), removing unneeded spaces, implementing is() to compare objects, !strict, refs #10625.<commit_after>dojo.provide("dojox.lang.tests.async");

dojo.require("dojox.lang.async");
dojo.require("dojox.lang.async.timeout");
dojo.require("dojox.lang.async.topic");

(function(){
	var async = dojox.lang.async,
		wait = async.timeout.from,
		QUANT     = 50, // ms
		MAX_TICKS = 5;

	function randomTimes(n){
		var a = [];
		for(var i = 0; i < n; ++i){
			a.push((Math.floor(Math.random() * MAX_TICKS) + 1) * QUANT);
		}
		return a;
	}

	function getMin(a){
		return Math.min.apply(Math, a);
	}

	function getMax(a){
		return Math.max.apply(Math, a);
	}

	function waitAndExpect(expected, ms){
		return function(value){
			console.log("waitAndExpect: ", value, ", expected: ", expected);
			if(expected !== value){
				console.log("ERROR: unexpected value");
				throw new Error("Unexpected value");
			}
			return wait(ms)();
		}
	}

	function identity(x){
		return x;
	}

	function is(a, b, visited){
		if(a === b){
			return true;
		}
		if(typeof a != typeof b){
			return false;
		}
		if(Object.prototype.toString.call(a) != Object.prototype.toString.call(b)){
			return false;
		}
		if(Object.prototype.toString.call(a) == "[object Function]"){
			return false;
		}
		if(Object.prototype.toString.call(a) == "[object Array]"){
			if(a.length !== b.length){
				return false;
			}
			for(var i = 0; i < a.length; ++i){
				if(!is(a[i], b[i], visited)){
					return false;
				}
				return true;
			}
		}
		if(typeof a == "object"){
			if(visited){
				for(var i = 0; i < visited.length; ++i){
					if(visited[i] === a || visited[i] === b){
						return true;
					}
				}
				visited.push(a, b);
			}else{
				visited = [a, b];
}
			var akeys = [];
			for(var i in a){
				akeys.push(i);
			}
			var bkeys = [];
			for(var i in b){
				bkeys.push(i);
			}
			akeys.sort();
			bkeys.sort();
			if(!is(akeys, bkeys)){
				return false;
			}
			for(var i = 0; i < akeys.length; ++i){
				if(!is(a[akeys[i]], b[bkeys[i]])){
					return false;
				}
			}
			return true;
		}
		return false;
	}

	var waitFor0 = waitAndExpect(0, 20),
		waitFor1 = waitAndExpect(1, 20),
		waitFor2 = waitAndExpect(2, 20);

	tests.register("dojox.lang.tests.async", [
		function smokeTest(){
			var a = randomTimes(1),
				r = new dojo.Deferred();
			wait(a[0])().addCallback(function(x){
				if(r == a[0]){
					console.log("ERROR: smokeTest: wrong result");
					throw new Error("smokeTest: wrong result");
				}
				r.callback();
			});
			return r;
		},
		function testSeq(){
			var a = randomTimes(5),
				fs = dojo.map(a, function(ms, i){
					return waitAndExpect(i && a[i - 1], ms);
				});
			return async.seq.apply(async, fs)(0).addCallback(function(value){
				if(a[a.length - 1] !== value){
					console.log("ERROR: testSeq: wrong time");
					throw new Error("testSeq: wrong time");
				}
			});
		},
		function testPar(){
			var a = randomTimes(5),
				fs = dojo.map(a, function(ms){
					return waitAndExpect(0, ms);
				});
			return async.par.apply(async, fs)(0).addCallback(function(value){
				console.log(a, " - ", value);
				if(!is(a, value)){
					console.log("ERROR: testPar: wrong time");
					throw new Error("testPar: wrong time");
				}
			});
		},
		function testAny(){
			var a = randomTimes(5),
				min = getMin(a),
				fs = dojo.map(a, function(ms){
					return waitAndExpect(0, ms);
				});
			return async.any.apply(async, fs)(0).addCallback(function(value){
				console.log(min, " - ", value);
				if(min !== value){
					console.log("ERROR: testAny: wrong time");
					throw new Error("testAny: wrong time");
				}
			});
		},
		function testSelect0(){
			return async.select(
				identity,
				waitFor0,
				waitFor1,
				waitFor2
			)(0);
		},
		function testSelect1(){
			return async.select(
				identity,
				waitFor0,
				waitFor1,
				waitFor2
			)(1);
		},
		function testSelect2(){
			return async.select(
				identity,
				waitFor0,
				waitFor1,
				waitFor2
			)(2);
		},
		function testIfThenT(){
			return async.ifThen(
				identity,
				waitFor1,
				waitFor0
			)(1);
		},
		function testIfThenF(){
			return async.ifThen(
				identity,
				waitFor1,
				waitFor0
			)(0);
		},
		function testLoop(){
			var counter = 0;
			return async.seq(
				async.loop(
					identity,
					function(ms){
						++counter;
						return wait(ms - 10)();
					}
				),
				function(){
					console.log(counter, " - ", 3);
					if(counter !== 3){
						console.log("ERROR: testLoop: wrong number of iterations");
						throw new Error("testLoop: wrong number of iterations");
					}
				}
			)(30);
		}
	]);
})();
<|endoftext|>


<commit_before>var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var UserSchema = new Schema({
  username: {
    type: String,
    required: true,
    index: { unique: true }
  },
  password_hash: {
    type: String,
    required: true
  },
  full_name: {
    type: String,
    required: true
  },
  url: {
    type: String,
    required: true
    index: { unique: true }
  },
  bio: String
});

module.exports = mongoose.model('User', UserSchema);<commit_msg>Included bcrypt into the appropriate models and cleaned up some data field names<commit_after>var bcrypt = require('bcrypt'),
    mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var UserSchema = new Schema({
  username: {
    type: String,
    required: true,
    index: { unique: true }
  },
  password: {
    type: String,
    required: true
  },
  full_name: {
    type: String,
    required: true
  },
  url: {
    type: String,
    required: true
    index: { unique: true }
  },
  bio: String
});

module.exports = mongoose.model('User', UserSchema);<|endoftext|>


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

import '@chopsui/tsmon-client';
import 'dashboard-metrics';
import * as PolymerAsync from '@polymer/polymer/lib/utils/async.js';
import {Debouncer} from '@polymer/polymer/lib/utils/debounce.js';
import {LitElement} from 'lit-element';
import {get} from 'dot-prop-immutable';
import {isProduction} from './utils.js';

import {
  DEFAULT_REDUCER_WRAPPERS,
  UPDATE,
  createSimpleStore,
  freezingReducer,
  registerReducers,
  renameReducer,
} from './simple-redux.js';

export const STORE = createSimpleStore({
  devtools: {
    // Do not record changes automatically when in a production environment.
    shouldRecordChanges: !isProduction(),

    // Increase the maximum number of actions stored in the history tree.
    // The oldest actions are removed once maxAge is reached.
    maxAge: 75,
  },
});

// Export the state store directly to window in order to facilitate debugging.
window.STORE = STORE;

/*
 * This base class mixes LitElement with Polymer-Redux and provides
 * utility functions to help data-bindings in elements perform minimal
 * computation without computed properties.
 */
export class ElementBase extends LitElement {
  constructor() {
    super();
    this.debounceJobs_ = new Map();
  }

  connectedCallback() {
    super.connectedCallback();
    this.unsubscribeRedux_ = STORE.subscribe(() =>
      this.stateChanged(STORE.getState()));
    this.stateChanged(STORE.getState());
  }

  stateChanged(rootState) {
    if (!this.statePath) return;
    Object.assign(this, get(rootState, this.statePath));
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.unsubscribeRedux_();
  }

  /**
    * Wrap Debouncer in a friendlier syntax.
    *
    * @param {*} jobName
    * @param {Function()} callback
    * @param {Object=} asyncModule See PolymerAsync.
    */
  debounce(jobName, callback, asyncModule = PolymerAsync.animationFrame) {
    this.debounceJobs_.set(jobName, Debouncer.debounce(
        this.debounceJobs_.get(jobName), asyncModule, callback));
  }
}

if (window.location.hostname === 'localhost') {
  // timeReducer should appear before freezingReducer so that the timing
  // doesn't include the overhead from freezingReducer. statePathReducer must
  // be last because it changes the function signature.
  DEFAULT_REDUCER_WRAPPERS.splice(1, 0, freezingReducer);
}

ElementBase.register = subclass => {
  customElements.define(subclass.is, subclass);
  if (subclass.reducers) {
    registerReducers(subclass.reducers, [
      renameReducer(subclass.name + '.'),
      ...DEFAULT_REDUCER_WRAPPERS,
    ]);
  }
};

export function maybeScheduleAutoReload(
    statePath, pred, callback, ms = (1000 * 60 * 60)) {
  const state = get(STORE.getState(), statePath);
  if (!state) return;
  if (state.reloadTimer) window.clearTimeout(state.reloadTimer);
  let reloadTimer;
  if (pred(state)) {
    // Automatically reload after some time.
    reloadTimer = window.setTimeout(callback, ms);
  }
  STORE.dispatch(UPDATE(statePath, {reloadTimer}));
}
<commit_msg>Dedup isDebug in spa.<commit_after>/* Copyright 2018 The Chromium Authors. All rights reserved.
   Use of this source code is governed by a BSD-style license that can be
   found in the LICENSE file.
*/
'use strict';

import '@chopsui/tsmon-client';
import 'dashboard-metrics';
import * as PolymerAsync from '@polymer/polymer/lib/utils/async.js';
import {Debouncer} from '@polymer/polymer/lib/utils/debounce.js';
import {LitElement} from 'lit-element';
import {get} from 'dot-prop-immutable';
import {isDebug, isProduction} from './utils.js';

import {
  DEFAULT_REDUCER_WRAPPERS,
  UPDATE,
  createSimpleStore,
  freezingReducer,
  registerReducers,
  renameReducer,
} from './simple-redux.js';

export const STORE = createSimpleStore({
  devtools: {
    // Do not record changes automatically when in a production environment.
    shouldRecordChanges: !isProduction(),

    // Increase the maximum number of actions stored in the history tree.
    // The oldest actions are removed once maxAge is reached.
    maxAge: 75,
  },
});

// Export the state store directly to window in order to facilitate debugging.
window.STORE = STORE;

/*
 * This base class mixes LitElement with Polymer-Redux and provides
 * utility functions to help data-bindings in elements perform minimal
 * computation without computed properties.
 */
export class ElementBase extends LitElement {
  constructor() {
    super();
    this.debounceJobs_ = new Map();
  }

  connectedCallback() {
    super.connectedCallback();
    this.unsubscribeRedux_ = STORE.subscribe(() =>
      this.stateChanged(STORE.getState()));
    this.stateChanged(STORE.getState());
  }

  stateChanged(rootState) {
    if (!this.statePath) return;
    Object.assign(this, get(rootState, this.statePath));
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.unsubscribeRedux_();
  }

  /**
    * Wrap Debouncer in a friendlier syntax.
    *
    * @param {*} jobName
    * @param {Function()} callback
    * @param {Object=} asyncModule See PolymerAsync.
    */
  debounce(jobName, callback, asyncModule = PolymerAsync.animationFrame) {
    this.debounceJobs_.set(jobName, Debouncer.debounce(
        this.debounceJobs_.get(jobName), asyncModule, callback));
  }
}

if (isDebug()) {
  // timeReducer should appear before freezingReducer so that the timing
  // doesn't include the overhead from freezingReducer. statePathReducer must
  // be last because it changes the function signature.
  DEFAULT_REDUCER_WRAPPERS.splice(1, 0, freezingReducer);
}

ElementBase.register = subclass => {
  customElements.define(subclass.is, subclass);
  if (subclass.reducers) {
    registerReducers(subclass.reducers, [
      renameReducer(subclass.name + '.'),
      ...DEFAULT_REDUCER_WRAPPERS,
    ]);
  }
};

export function maybeScheduleAutoReload(
    statePath, pred, callback, ms = (1000 * 60 * 60)) {
  const state = get(STORE.getState(), statePath);
  if (!state) return;
  if (state.reloadTimer) window.clearTimeout(state.reloadTimer);
  let reloadTimer;
  if (pred(state)) {
    // Automatically reload after some time.
    reloadTimer = window.setTimeout(callback, ms);
  }
  STORE.dispatch(UPDATE(statePath, {reloadTimer}));
}
<|endoftext|>


<commit_before>CardshifterApp.controller("DeckbuilderController", function($scope) {
    var DECK_STORAGE = "CARDSHIFTER_DECK_STORAGE";

    $scope.cards = [];
    $scope.maxCards = 0;
    $scope.minCards = 0;
    $scope.currentDeck = {};
    $scope.cardInfo = {};
    $scope.currentDeckName = "untitled";
    $scope.savedDecks = [];
    $scope.doneLoading = false;

    if(!localStorage.getItem(DECK_STORAGE)) {
        var json = "{\"decks\": {";
        for(var i = 0, length = availableGameMods.length; i < length; i++) {
            json += "\"" + availableGameMods[i] + "\":[],";
        }
        json = json.slice(0, -1); // remove last comma
        json += "}}";
        localStorage.setItem(DECK_STORAGE, json);
    }

    CardshifterServerAPI.setMessageListener(function(cardInformation) {
        var deck = cardInformation.configs.Deck;

        for(var card in deck.cardData) {
            if(deck.cardData.hasOwnProperty(card)) {
                deck.cardData[card].max = deck.max[card] || deck.maxPerCard;
                $scope.currentDeck[deck.cardData[card].id] = 0;
            }
        }

        $scope.cards = deck.cardData;
        $scope.maxCards = deck.maxSize;
        $scope.minCards = deck.minSize;
        updateSavedDecks();
        $scope.doneLoading = true;

        $scope.$apply();
    }, ["playerconfig"]);

    $scope.decrement = function(card) {
        if($scope.currentDeck[card.id] !== 0) {
            $scope.currentDeck[card.id]--;
        }
    };
    $scope.increment = function(card) {
        if($scope.getTotalSelected() !== $scope.maxCards &&
           $scope.currentDeck[card.id] !== card.max) {
            $scope.currentDeck[card.id]++;
        }
    };

    $scope.getTotalSelected = function() {
        var total = 0;
        for(var card in $scope.currentDeck) {
            if($scope.currentDeck.hasOwnProperty(card)) {
                total += $scope.currentDeck[card];
            }

        }
        return total;
    };

    $scope.showDetails = function(card) {
        var props = card.properties;
        $scope.cardInfo = {
            name: props.name,
            flavor: props.flavor,
            type: props.creatureType,
            health: props.HEALTH,
            attack: props.ATTACK,
            sickness: props.SICKNESS
        };
    };

    $scope.saveDeck = function() {
        if($scope.getTotalSelected() === $scope.minCards) {
            if($scope.deckName) {
                if(!getDeckIndex($scope.deckName)) { // if deck exists
                    var savedDecks = JSON.parse(localStorage.getItem(DECK_STORAGE));

                    var newDeck = {
                        name: $scope.deckName,
                        cards: $scope.currentDeck
                    };

                    savedDecks.decks[currentUser.game.mod].push(newDeck);
                    localStorage.setItem(DECK_STORAGE, JSON.stringify(savedDecks));
                    updateSavedDecks();

                    $scope.switchDeck(newDeck);
                } else {
                    console.log("deck already exists");     // bad looking nesting
                }
            } else {
                console.log("please enter name");
            }
        } else {
            console.log("not enough cards");
        }
    };
    $scope.switchDeck = function(deck) {
        $scope.currentDeckName = deck.name;
        $scope.currentDeck = deck.cards;
    };
    $scope.deleteDeck = function(deckName) {
        var savedDecks = JSON.parse(localStorage.getItem(DECK_STORAGE));
        savedDecks.decks[currentUser.game.mod].splice(getDeckIndex(deckName), 1);
        localStorage.setItem(DECK_STORAGE, JSON.stringify(savedDecks));

        updateSavedDecks();
    };

    function updateSavedDecks() {
        $scope.savedDecks = JSON.parse(localStorage.getItem(DECK_STORAGE)).decks[currentUser.game.mod];
    }
    function getDeckIndex(deckName) {
        for(var i = 0, length = $scope.savedDecks.length; i < length; i++) {
            if($scope.savedDecks[i].name === deckName) {
                return i;
            }
        }
        return false;
    }
});<commit_msg>added functions for redirecting to the game screen and to the lobby<commit_after>CardshifterApp.controller("DeckbuilderController", function($scope, $rootScope, $location) {
    var DECK_STORAGE = "CARDSHIFTER_DECK_STORAGE";

    $scope.cards = [];
    $scope.maxCards = 0;
    $scope.minCards = 0;
    $scope.currentDeck = {};
    $scope.cardInfo = {};
    $scope.currentDeckName = "untitled";
    $scope.savedDecks = [];
    $scope.doneLoading = false;
    $scope.enteringGame = currentUser.game.id;

    if(!localStorage.getItem(DECK_STORAGE)) {
        var json = "{\"decks\": {";
        for(var i = 0, length = availableGameMods.length; i < length; i++) {
            json += "\"" + availableGameMods[i] + "\":[],";
        }
        json = json.slice(0, -1); // remove last comma
        json += "}}";
        localStorage.setItem(DECK_STORAGE, json);
    }

    CardshifterServerAPI.setMessageListener(function(cardInformation) {
        var deck = cardInformation.configs.Deck;

        for(var card in deck.cardData) {
            if(deck.cardData.hasOwnProperty(card)) {
                deck.cardData[card].max = deck.max[card] || deck.maxPerCard;
                $scope.currentDeck[deck.cardData[card].id] = 0;
            }
        }

        $scope.cards = deck.cardData;
        $scope.maxCards = deck.maxSize;
        $scope.minCards = deck.minSize;
        updateSavedDecks();
        $scope.doneLoading = true;

        $scope.$apply();
    }, ["playerconfig"]);

    $scope.decrement = function(card) {
        if($scope.currentDeck[card.id] !== 0) {
            $scope.currentDeck[card.id]--;
        }
    };
    $scope.increment = function(card) {
        if($scope.getTotalSelected() !== $scope.maxCards &&
           $scope.currentDeck[card.id] !== card.max) {
            $scope.currentDeck[card.id]++;
        }
    };

    $scope.getTotalSelected = function() {
        var total = 0;
        for(var card in $scope.currentDeck) {
            if($scope.currentDeck.hasOwnProperty(card)) {
                total += $scope.currentDeck[card];
            }

        }
        return total;
    };

    $scope.showDetails = function(card) {
        var props = card.properties;
        $scope.cardInfo = {
            name: props.name,
            flavor: props.flavor,
            type: props.creatureType,
            health: props.HEALTH,
            attack: props.ATTACK,
            sickness: props.SICKNESS
        };
    };

    $scope.saveDeck = function() {
        if($scope.getTotalSelected() === $scope.minCards) {
            if($scope.deckName) {
                if(!getDeckIndex($scope.deckName)) { // if deck exists
                    var savedDecks = JSON.parse(localStorage.getItem(DECK_STORAGE));

                    var newDeck = {
                        name: $scope.deckName,
                        cards: $scope.currentDeck
                    };

                    savedDecks.decks[currentUser.game.mod].push(newDeck);
                    localStorage.setItem(DECK_STORAGE, JSON.stringify(savedDecks));
                    updateSavedDecks();

                    $scope.switchDeck(newDeck);
                } else {
                    console.log("deck already exists");     // bad looking nesting
                }
            } else {
                console.log("please enter name");
            }
        } else {
            console.log("not enough cards");
        }
    };
    $scope.switchDeck = function(deck) {
        $scope.currentDeckName = deck.name;
        $scope.currentDeck = deck.cards;
    };
    $scope.deleteDeck = function(deckName) {
        var savedDecks = JSON.parse(localStorage.getItem(DECK_STORAGE));
        savedDecks.decks[currentUser.game.mod].splice(getDeckIndex(deckName), 1);
        localStorage.setItem(DECK_STORAGE, JSON.stringify(savedDecks));

        updateSavedDecks();
    };

    $scope.enterGame = function() {
        if($scope.getTotalSelected() === $scope.minCards) {
            console.log("enter game");
        } else {
            console.log("not enough cards");
        }
    };
    $scope.goBack = function() {
        $rootScope.$apply(function() {
            $location.path("/lobby");
        });
    };

    function updateSavedDecks() {
        $scope.savedDecks = JSON.parse(localStorage.getItem(DECK_STORAGE)).decks[currentUser.game.mod];
    }
    function getDeckIndex(deckName) {
        for(var i = 0, length = $scope.savedDecks.length; i < length; i++) {
            if($scope.savedDecks[i].name === deckName) {
                return i;
            }
        }
        return false;
    }
});<|endoftext|>


<commit_before>#!/usr/bin/env node

require('./proof')(1, function (step, tmp, Strata, serialize, equal) {
    var fs = require('fs'), strata, records = [], count = 0

    function tracer (type, object, callback) {
        switch (type) {
        case 'lock':
            if (object.address == 0 && object.exclusive) {
                callback(new Error('bogus'))
                break
            }
        default:
            callback()
        }
    }

    step(function () {
        serialize(__dirname + '/fixtures/split-race.before.json', tmp, step())
    }, function () {
        strata = new Strata({ directory: tmp, leafSize: 3, branchSize: 3, tracer: tracer })
        strata.open(step())
    }, function () {
        strata.mutator('d', step())
    }, function (cursor) {
        step(function () {
            cursor.insert('d', 'd', ~ cursor.index, step())
        }, function () {
            cursor.unlock()
        })
    }, [function (records) {
        strata.balance(step())
    }, function (_, error) {
        equal(error.message, 'bogus', 'caught')
    }], function(actual, expected) {
        strata.close(step())
    })
})
<commit_msg>Tidy `t/coverage/upgrade.t.js`.<commit_after>#!/usr/bin/env node

require('./proof')(1, function (step, Strata, tmp, serialize, equal) {
    var strata

    function tracer (type, object, callback) {
        switch (type) {
        case 'lock':
            if (object.address == 0 && object.exclusive) {
                callback(new Error('bogus'))
                break
            }
        default:
            callback()
        }
    }

    step(function () {
        serialize(__dirname + '/fixtures/split-race.before.json', tmp, step())
    }, function () {
        strata = new Strata({ directory: tmp, leafSize: 3, branchSize: 3, tracer: tracer })
        strata.open(step())
    }, function () {
        strata.mutator('d', step())
    }, function (cursor) {
        step(function () {
            cursor.insert('d', 'd', ~ cursor.index, step())
        }, function () {
            cursor.unlock()
        })
    }, [function (records) {
        strata.balance(step())
    }, function (_, error) {
        equal(error.message, 'bogus', 'caught')
    }], function(actual, expected) {
        strata.close(step())
    })
})
<|endoftext|>


<commit_before>'use babel';

// Lazy load node modules
let fs;
let mime;
let imageSize;
let moment;

const KIBIBYTE_BASE = 1024;

const SI_BASE = 1000;

const FORMAT_24_HOUR = 'HH:mm:ss';

const FORMAT_12_HOUR = 'h:mm:ss a';

const KIBIBYTE_REPRESENTATION = [
  'bytes',
  'KiB',
  'MiB',
  'GiB',
  'TiB',
  'PiB',
  'EiB',
  'ZiB',
  'YiB',
];

const SI_REPRESENTATION = [
  'bytes',
  'KB',
  'MB',
  'GB',
  'TB',
  'PB',
  'EB',
  'ZB',
  'YB',
];

const IMAGE_FORMATS = [
  'image/bmp',
  'imagem/jpeg',
  'image/png',
  'image/gif',
  'image/tiff',
  'image/x-tiff',
  'image/webp',
  'image/vnd.adobe.photoshop',
];

function loadFileInfoAsync(filepath) {
  fs = fs || require('fs');
  return new Promise((resolve, reject) => {
    if (filepath) {
      fs.stat(filepath, (err, stats) => {
        if (!err) {
          const info = {
            absolutePath: filepath,
            size: stats.size,
            dateCreated: stats.birthtime,
            dateChanged: stats.mtime,
          };
          resolve(info);
        } else {
          reject(err);
        }
      });
    } else {
      reject(new Error('Please provide a valid filepath'));
    }
  });
}

function addPrettySize(info, { useKibibyteRepresentation = true }) {
  const { size } = info;
  if (size === 0) return Object.assign(info, { prettySize: '0 bytes' });
  if (size === 1) return Object.assign(info, { prettySize: '1 byte' });
  const base = (useKibibyteRepresentation) ? KIBIBYTE_BASE : SI_BASE;
  const suffixes = (useKibibyteRepresentation) ? KIBIBYTE_REPRESENTATION : SI_REPRESENTATION;
  const scale = Math.floor(Math.log(size) / Math.log(base));
  const activeSuffix = suffixes[scale];
  const scaledSize = size / Math.pow(base, scale);
  // Round size with a decimal precision of 2
  const fixedScale = Math.round(`${scaledSize}e+2`);
  const roundedSize = Number(`${fixedScale}e-2`);
  const prettySize = `${roundedSize} ${activeSuffix}`;
  return Object.assign(info, { prettySize });
}

function addMimeTypeInfo(info) {
  mime = mime || require('mime');
  const mimeType = mime.lookup(info.absolutePath);
  return Object.assign(info, { mimeType });
}

function addImageInfo(info) {
  if (!info.mimeType || !IMAGE_FORMATS.includes(info.mimeType)) return info;
  imageSize = imageSize || require('image-size');
  const dimmensions = imageSize(info.absolutePath);
  return Object.assign(info, { dimmensions });
}

function addPrettyDateInfo(info, { use24HourFormat = true }) {
  moment = moment || require('moment');
  const hourFormat = (use24HourFormat) ? FORMAT_24_HOUR : FORMAT_12_HOUR;
  const dateCreated = moment(info.dateCreated).format(`MMMM Do YYYY, ${hourFormat}`);
  const dateChanged = moment(info.dateChanged).format(`MMMM Do YYYY, ${hourFormat}`);
  return Object.assign(info, { dateCreated, dateChanged });
}

export {
  loadFileInfoAsync,
  addPrettySize,
  addMimeTypeInfo,
  addImageInfo,
  addPrettyDateInfo,
};
<commit_msg>Use ISO strings as the default way to represent dates<commit_after>'use babel';

// Lazy load node modules
let fs;
let mime;
let imageSize;
let moment;

const KIBIBYTE_BASE = 1024;

const SI_BASE = 1000;

const FORMAT_24_HOUR = 'HH:mm:ss';

const FORMAT_12_HOUR = 'h:mm:ss a';

const KIBIBYTE_REPRESENTATION = [
  'bytes',
  'KiB',
  'MiB',
  'GiB',
  'TiB',
  'PiB',
  'EiB',
  'ZiB',
  'YiB',
];

const SI_REPRESENTATION = [
  'bytes',
  'KB',
  'MB',
  'GB',
  'TB',
  'PB',
  'EB',
  'ZB',
  'YB',
];

const IMAGE_FORMATS = [
  'image/bmp',
  'imagem/jpeg',
  'image/png',
  'image/gif',
  'image/tiff',
  'image/x-tiff',
  'image/webp',
  'image/vnd.adobe.photoshop',
];

function loadFileInfoAsync(filepath) {
  fs = fs || require('fs');
  return new Promise((resolve, reject) => {
    if (filepath) {
      fs.stat(filepath, (err, stats) => {
        if (!err) {
          const info = {
            absolutePath: filepath,
            size: stats.size,
            dateCreated: stats.birthtime.toISOString(),
            dateChanged: stats.mtime.toISOString(),
          };
          resolve(info);
        } else {
          reject(err);
        }
      });
    } else {
      reject(new Error('Please provide a valid filepath'));
    }
  });
}

function addPrettySize(info, { useKibibyteRepresentation = true }) {
  const { size } = info;
  if (size === 0) return Object.assign(info, { prettySize: '0 bytes' });
  if (size === 1) return Object.assign(info, { prettySize: '1 byte' });
  const base = (useKibibyteRepresentation) ? KIBIBYTE_BASE : SI_BASE;
  const suffixes = (useKibibyteRepresentation) ? KIBIBYTE_REPRESENTATION : SI_REPRESENTATION;
  const scale = Math.floor(Math.log(size) / Math.log(base));
  const activeSuffix = suffixes[scale];
  const scaledSize = size / Math.pow(base, scale);
  // Round size with a decimal precision of 2
  const fixedScale = Math.round(`${scaledSize}e+2`);
  const roundedSize = Number(`${fixedScale}e-2`);
  const prettySize = `${roundedSize} ${activeSuffix}`;
  return Object.assign(info, { prettySize });
}

function addMimeTypeInfo(info) {
  mime = mime || require('mime');
  const mimeType = mime.lookup(info.absolutePath);
  return Object.assign(info, { mimeType });
}

function addImageInfo(info) {
  if (!info.mimeType || !IMAGE_FORMATS.includes(info.mimeType)) return info;
  imageSize = imageSize || require('image-size');
  const dimmensions = imageSize(info.absolutePath);
  return Object.assign(info, { dimmensions });
}

function addPrettyDateInfo(info, { use24HourFormat = true }) {
  moment = moment || require('moment');
  const hourFormat = (use24HourFormat) ? FORMAT_24_HOUR : FORMAT_12_HOUR;
  const dateCreated = moment(info.dateCreated).format(`MMMM Do YYYY, ${hourFormat}`);
  const dateChanged = moment(info.dateChanged).format(`MMMM Do YYYY, ${hourFormat}`);
  return Object.assign(info, { dateCreated, dateChanged });
}

export {
  loadFileInfoAsync,
  addPrettySize,
  addMimeTypeInfo,
  addImageInfo,
  addPrettyDateInfo,
};
<|endoftext|>


<commit_before>exports.browsers = [
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 7',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8.1',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'firefox'
    }
    ,
    // {
    //     browserName: "safari",
    //     platform: "OS X 10.11",
    //     version: "9"
    // },
    // {
    //     platformName: 'iOS',
    //     platformVersion: '8.4',
    //     deviceName: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // },
    // {
    //     platformName: 'iOS',
    //     platformVersion: '9.2',
    //     deviceName: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // },
    // {
    //     platformName: 'iOS',
    //     platformVersion: '9.3',
    //     deviceName: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // },

    {
        platform: 'Linux',
        browserName: 'chrome'
    },
    {
        platform: 'Linux',
        browserName: 'firefox'
    },

    {
        platform: 'OS X 10.10',
        browserName: 'chrome'
    },
    {
        platform: 'OS X 10.10',
        browserName: 'firefox'
    },
    {
        platform: 'OS X 10.10',
        browserName: 'safari'
    },

    {
        platform: 'OS X 10.11',
        browserName: 'chrome'
    },
    {
        platform: 'OS X 10.11',
        browserName: 'firefox'
    },
    {
        platform: 'OS X 10.11',
        browserName: 'safari'
    },

    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    }
];
<commit_msg>Comment ipad Safari tests<commit_after>exports.browsers = [
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 7',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 7',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8',
        browserName: 'firefox'
    },

    {
        platform: 'Windows 8.1',
        browserName: 'internet explorer',
        version: '11.0'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'chrome'
    },
    {
        platform: 'Windows 8.1',
        browserName: 'firefox'
    }
    ,
    // {
    //     browserName: "safari",
    //     platform: "OS X 10.11",
    //     version: "9"
    // },
    // {
    //     platformName: 'iOS',
    //     platformVersion: '8.4',
    //     deviceName: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // },
    // {
    //     platformName: 'iOS',
    //     platformVersion: '9.2',
    //     deviceName: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // },
    // {
    //     platformName: 'iOS',
    //     platformVersion: '9.3',
    //     deviceName: 'iPad Simulator',
    //     browserName: 'Safari',
    //     deviceOrientation: 'landscape'
    // },

    {
        platform: 'Linux',
        browserName: 'chrome'
    },
    {
        platform: 'Linux',
        browserName: 'firefox'
    },

    {
        platform: 'OS X 10.10',
        browserName: 'chrome'
    },
    {
        platform: 'OS X 10.10',
        browserName: 'firefox'
    }
    ,
    // {
    //     platform: 'OS X 10.10',
    //     browserName: 'safari'
    // },

    {
        platform: 'OS X 10.11',
        browserName: 'chrome'
    },
    {
        platform: 'OS X 10.11',
        browserName: 'firefox'
    },
    // {
    //     platform: 'OS X 10.11',
    //     browserName: 'safari'
    // },

    {
        platform: 'Windows 7',
        browserName: 'internet explorer',
        version: '10.0'
    },
    {
        platform: 'Windows 8',
        browserName: 'internet explorer',
        version: '10.0'
    }
];
<|endoftext|>