<commit_before>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatNumber, formatPercentage } from 'common/format';

import Analyzer from 'parser/core/Analyzer';

import StatisticBox, { STATISTIC_ORDER } from 'interface/others/StatisticBox';

const damagingCasts = [SPELLS.EYE_OF_THE_STORM.id, SPELLS.WIND_GUST.id, SPELLS.CALL_LIGHTNING.id];
const CALL_LIGHTNING_BUFF_DURATION = 15000;

class PrimalStormElemental extends Analyzer {
  eotsCasts = 0;
  pseCasts = 0;
  lastCLCastTimestamp = 0;

  usedCasts = {
    'Eye of the Storm': false,
    'Wind Gust': false,
    'Call Lightning': false,
  };

  damageGained = 0;
  maelstromGained = 0;
  badCasts=0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTalent(SPELLS.PRIMAL_ELEMENTALIST_TALENT.id)
      && this.selectedCombatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id);
  }

  on_byPlayer_cast(event) {
    if (event.ability.guid === SPELLS.STORM_ELEMENTAL_TALENT.id){
      return;
    }
    this.pseCasts+=1;
  }

  on_cast(event) {
    switch(event.ability.guid) {
      case SPELLS.EYE_OF_THE_STORM.id:
        this.usedCasts['Eye of the Storm']=true;
        break;
      case SPELLS.WIND_GUST.id:
        this.usedCasts['Wind Gust']=true;
        break;
      case SPELLS.CALL_LIGHTNING.id:
        this.usedCasts['Call Lightning']=true;
        this.lastCLCastTimestamp=event.timestamp;
        break;
      default:
        break;
    }
  }

  on_damage(event) {
    if (!damagingCasts.includes(event.ability.guid)) {
      return;
    }
    this.damageGained+=event.amount;

    if(event.ability.guid !== SPELLS.CALL_LIGHTNING.id) {
      if(event.timestamp>this.lastCLCastTimestamp+CALL_LIGHTNING_BUFF_DURATION){
        this.badCasts+=1;
      }
    }
  }

  get damagePercent() {
    return this.owner.getPercentageOfTotalDamageDone(this.damageGained);
  }

  get damagePerSecond() {
    return this.damageGained / (this.owner.fightDuration / 1000);
  }

  suggestions(when) {
    const unusedSpells = Object.keys(this.usedCasts).filter(key => !this.usedCasts[key]);
    const unusedSpellsString = unusedSpells.join(', ');
    const unusedSpellsCount = unusedSpells.length;
    when(unusedSpellsCount).isGreaterThan(0)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest( Your Storm Elemental is not using all of it's spells. Check if Wind Gust and Eye Of The Storm are set to autocast and you are using Call Lightning.)
          .icon(SPELLS.STORM_ELEMENTAL_TALENT.icon)
          .actual(`${formatNumber(unusedSpellsCount)} spells not used by your Storm Elemental(${unusedSpellsString})`)
          .recommended(`You should be using all spells of your Storm Elemental.`)
          .major(recommended+1);
      });

    when(this.badCasts).isGreaterThan(0)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(You are not using <SpellLink id={SPELLS.CALL_LIGHTNING.id} /> on cooldown.)
          .icon(SPELLS.STORM_ELEMENTAL_TALENT.icon)
          .actual(`${formatNumber(this.badCasts)} casts done by your Storm Elemental without the "Call Lightning"-Buff.}`)
          .recommended(`You should be recasting "Call Lightning" before the buff drops off.`)
          .major(recommended+5);
      });
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.STORM_ELEMENTAL_TALENT.id} />}
        value={`~ ${formatPercentage(this.damagePercent)} %`}
        label="Of total damage"
        tooltip={`Buffed casts contributed ${formatNumber(this.damagePerSecond)} DPS (${formatNumber(this.damageGained)} total damage).`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL();
}

export default PrimalStormElemental;
<commit_msg>use position instead of STATISTIC_ORDER for PSE as well<commit_after>import React from 'react';

import SPELLS from 'common/SPELLS';
import SpellIcon from 'common/SpellIcon';
import SpellLink from 'common/SpellLink';
import { formatNumber, formatPercentage } from 'common/format';

import Analyzer from 'parser/core/Analyzer';

import StatisticBox, { STATISTIC_ORDER } from 'interface/others/StatisticBox';

const damagingCasts = [SPELLS.EYE_OF_THE_STORM.id, SPELLS.WIND_GUST.id, SPELLS.CALL_LIGHTNING.id];
const CALL_LIGHTNING_BUFF_DURATION = 15000;

class PrimalStormElemental extends Analyzer {
  eotsCasts = 0;
  pseCasts = 0;
  lastCLCastTimestamp = 0;

  usedCasts = {
    'Eye of the Storm': false,
    'Wind Gust': false,
    'Call Lightning': false,
  };

  damageGained = 0;
  maelstromGained = 0;
  badCasts=0;

  constructor(...args) {
    super(...args);
    this.active = this.selectedCombatant.hasTalent(SPELLS.PRIMAL_ELEMENTALIST_TALENT.id)
      && this.selectedCombatant.hasTalent(SPELLS.STORM_ELEMENTAL_TALENT.id);
  }

  on_byPlayer_cast(event) {
    if (event.ability.guid === SPELLS.STORM_ELEMENTAL_TALENT.id){
      return;
    }
    this.pseCasts+=1;
  }

  on_cast(event) {
    switch(event.ability.guid) {
      case SPELLS.EYE_OF_THE_STORM.id:
        this.usedCasts['Eye of the Storm']=true;
        break;
      case SPELLS.WIND_GUST.id:
        this.usedCasts['Wind Gust']=true;
        break;
      case SPELLS.CALL_LIGHTNING.id:
        this.usedCasts['Call Lightning']=true;
        this.lastCLCastTimestamp=event.timestamp;
        break;
      default:
        break;
    }
  }

  on_damage(event) {
    if (!damagingCasts.includes(event.ability.guid)) {
      return;
    }
    this.damageGained+=event.amount;

    if(event.ability.guid !== SPELLS.CALL_LIGHTNING.id) {
      if(event.timestamp>this.lastCLCastTimestamp+CALL_LIGHTNING_BUFF_DURATION){
        this.badCasts+=1;
      }
    }
  }

  get damagePercent() {
    return this.owner.getPercentageOfTotalDamageDone(this.damageGained);
  }

  get damagePerSecond() {
    return this.damageGained / (this.owner.fightDuration / 1000);
  }

  suggestions(when) {
    const unusedSpells = Object.keys(this.usedCasts).filter(key => !this.usedCasts[key]);
    const unusedSpellsString = unusedSpells.join(', ');
    const unusedSpellsCount = unusedSpells.length;
    when(unusedSpellsCount).isGreaterThan(0)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest( Your Storm Elemental is not using all of it's spells. Check if Wind Gust and Eye Of The Storm are set to autocast and you are using Call Lightning.)
          .icon(SPELLS.STORM_ELEMENTAL_TALENT.icon)
          .actual(`${formatNumber(unusedSpellsCount)} spells not used by your Storm Elemental(${unusedSpellsString})`)
          .recommended(`You should be using all spells of your Storm Elemental.`)
          .major(recommended+1);
      });

    when(this.badCasts).isGreaterThan(0)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(You are not using <SpellLink id={SPELLS.CALL_LIGHTNING.id} /> on cooldown.)
          .icon(SPELLS.STORM_ELEMENTAL_TALENT.icon)
          .actual(`${formatNumber(this.badCasts)} casts done by your Storm Elemental without the "Call Lightning"-Buff.}`)
          .recommended(`You should be recasting "Call Lightning" before the buff drops off.`)
          .major(recommended+5);
      });
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.STORM_ELEMENTAL_TALENT.id} />}
        position={STATISTIC_ORDER.OPTIONAL()}
        value={`~ ${formatPercentage(this.damagePercent)} %`}
        label="Of total damage"
        tooltip={`Buffed casts contributed ${formatNumber(this.damagePerSecond)} DPS (${formatNumber(this.damageGained)} total damage).`}
      />
    );
  }
}

export default PrimalStormElemental;
<|endoftext|>


<commit_before>// account.js

var org = {

	// added on script load
	config: {
						endpoint: { hzws: 'http://api.spokanelibrary.org/v2/hzws/' }
					 }

	// added on authentication
	,user: {}

	// called on script load
	,init: function() {
		_self = this;

		// setup variables
		this.user = $('#spl-account-summary').data('account');
		//console.log(this.user);

		// init ui
		this.initMyAccount();

	} // init()

, initMyAccount: function() {

		this.initTabs();

		this.initCko();

		this.initHolds();

		this.initLists();

		this.initProfile();

  } // initMyAccount()

, initTabs: function() {
		// apply hash onload
		if ( window.location.hash ) {
			$('a[href="'+window.location.hash+'"]').tab('show')
		}

		// apply hashchange on tab event
		//$('body').on('click', '[data-toggle="tab"]', function(e) {
		$('[data-toggle="tab"]').click(function(e) {
			window.location.hash = $(this).attr('href');
		});

	} // initTabs()

, initCko: function() {
		var $form = $('#spl-form-cko');

		this.toggleCheckboxGroup('.spl-field-cko-select-all'
														,'.spl-field-cko-select-item');

		$form.on('submit', function(e) {
			if ( 'ajax' == $(this).data('process') ) {
				e.preventDefault();

				var barcodes = new Array;
				$('.spl-field-cko-select-item:checked').each(function() {
					barcodes.push( $(this).data('barcode') );
				});

				if ( barcodes.length > 0 ) {
					_self.ckoRenew(barcodes);
				} else {
					alert('Please select item(s) to renew.');
				}
			}

		});

	} // initCko()

, ckoRenew: function(barcodes) {
		var $form = $('#spl-form-cko');
		var $submit = $('.spl-submit-cko');
		var $hidden = $('#spl-field-cko-renewal');

		$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'renew' //'renew' //'trace'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	barcodes: barcodes
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			$hidden.val(JSON.stringify(obj));
			$form.data('process', 'http').submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

	} // ckoRenew()

, initHolds: function() {
		this.initHoldsReady();
		this.initHoldsPending();

	} // initHolds()

, initHoldsReady: function() {

		var $cancel = $('.spl-field-cancel-hold-ready');

		$cancel.on('click', function(e) {
			var holdkey = $(this).data('holdkey');
			if ( holdkey ) {
				$cancel.button('loading');
				_self.cancelHold(holdkey);
			}
		});
}

, cancelHold: function(holdKey) {
		//console.log( holdKey );
		var $form = $('#spl-form-holds-ready');

		$.ajax({
	    url: this.config.endpoint.hzws+'cancel'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	holdKey: holdKey
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			//console.log(obj);
			//$submit.button('reset');
			//$form.data('process', 'http').submit();
			$form.submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

}

, initHoldsPending: function() {

		this.initHoldsPendingSuspend();

		var $form = $('#spl-form-holds-pending');

		this.toggleCheckboxGroup('.spl-field-holds-pending-select-all'
														,'.spl-field-holds-pending-select-item');

		$('.spl-field-holds-pending-control').click(function(e) {
			$form.data('action', $(this).data('action')).submit();
		});

		$form.on('submit', function(e) {
			if ( 'ajax' == $(this).data('process') ) {
				e.preventDefault();

				var holdkeys = new Array;
				$('.spl-field-holds-pending-select-item:checked').each(function() {
					holdkeys.push( $(this).data('holdkey') );
				});

				if ( holdkeys.length > 0 ) {
					_self.holdsPendingUpdate($(this).data('action'), holdkeys);
					$(this).data('action', '');
				} else {
					alert('Please select hold requests(s).');
				}

			}

		});

	} // initHoldsPending()

, initHoldsPendingSuspend: function() {
		var $suspend = $('.spl-field-holds-pending-suspend');
		var $date = $('.spl-field-holds-pending-date');
		$date.on('change', function(e) {
			if ( $(this).val().length > 0 ) {
				$suspend.attr('disabled', false);
			} else {
				$suspend.attr('disabled', true);
			}
		});
		// disallow dates in the past
		$date.datepicker({startDate: new Date() });

} // initHoldsPendingSuspend()

, holdsPendingUpdate: function(action, holdkeys) {
		var $form = $('#spl-form-holds-pending');
		var $submit = $('.spl-field-holds-pending-control');
		var $hidden = $('#spl-field-holds-pending-update');

		var $location = $('.spl-field-holds-pending-location');
		var $date = $('.spl-field-holds-pending-date');

		$submit.button('loading'); //$submit.button('reset');

		var data = { params: {sessionToken: this.user.sessionToken
	    									,	holdKeys: holdkeys
	    									}
	    					}

		var endpoint;

		switch ( action ) {
			case 'cancel':
				endpoint = 'cancel';
				break;
			case 'pickup':
				endpoint = 'pickup';
				data.params.newLocation = $location.val();
				break;
			case 'suspend':
				endpoint = 'suspend';
				data.params.suspendEndDate = $date.val();
				break;
			case 'resume':
				endpoint = 'resume';
				break;
			default:
				endpoint = null;
				break;
		}

		if ( null != typeof(endpoint) ) {
			$.ajax({
		    url: this.config.endpoint.hzws+endpoint
	    , data: data
		  })
		  .done(function(obj) {
		  	// pass results through
				$hidden.val(JSON.stringify(obj));
				//console.log(obj);
				//$submit.button('reset');
				$form.data('process', 'http').submit();
		  })
		  .fail(function() {
		  })
		  .always(function() {
		  });
		}

	} // holdsPendingUpdate()

, initLists: function() {

		this.initListToggle();
		this.initListControl();
		this.initListCreate();
		//this.initListRemove();
		//this.initListMove();

	} // initLists()

, initListControl: function() {

		$('.spl-field-list-control').click(function(e) {
			var list = $(this).data('list');
			var action = $(this).data('action');

			var form = '#spl-form-list-control-'+list;
			var $form = $(form);

			var titlekeys = new Array;
			$('.spl-field-list-select-item:checked', form).each(function() {
				titlekeys.push( $(this).data('titlekey') );
			});

			console.log( $form.data('process') );
			//$form.submit();

			$form.on('submit', function(e) {

				if ( 'ajax' == $(this).data('process') ) {
					e.preventDefault();

					if ( titlekeys.length > 0 ) {
						_self.updateList(list, action, titlekeys);
					} else {
						alert('Please select title(s).');
					}
				}

			});

		});

	} // initListControl

, updateList: function(list, action, titlekeys) {

		console.log( list );
		console.log( action );
		console.log( titlekeys );

} // updateList

, initListToggle: function() {

		$('.spl-list-panel').on('show.bs.collapse', function() {

			console.log( '.spl-field-list-select-all, #'+$(this).attr('id') );

			_self.toggleCheckboxGroup('.spl-field-list-select-all'
															,'.spl-field-list-select-item'
															,'#'+$(this).attr('id')
															);

		});

	} // initListToggle

, initListMove: function() {
		var $move = $('.spl-field-list-move-control');

		$move.on('click', function(e) {
			e.preventDefault();
			//$move.button('loading');

			var from =  $(this).data('list');
			if ( from ) {

				var titlekeys = new Array;
				$('.spl-field-list-select-item:checked', '#spl-form-list-control-'+from).each(function() {
					titlekeys.push( $(this).data('titlekey') );
				});

				if ( titlekeys.length > 0 ) {
					//_self.holdsPendingUpdate($(this).data('action'), holdkeys);
					//$(this).data('action', '');
					var $to = $('#spl-field-list-move-to-'+from);
					var to = $to.val();
					_self.moveListItems(from, to, titlekeys);
				} else {
					alert('Please select list item(s).');
				}
			}

		});
} // initListMove()

, moveListItems: function(from, to, titleKeys) {

		$form = $('#spl-form-list-control-'+from);
		//$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'move'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	listKeyFrom: from
    									, listKeyTo: to
    									, titleKeys: titleKeys
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			console.log(obj);
			//$submit.button('reset');
			//$form.data('process', 'http').submit();
			//$form.submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

} // moveList()

, initListRemove: function() {

		var $remove = $('.spl-field-list-remove-control');

		$remove.on('click', function(e) {
			e.preventDefault();
			$remove.button('loading');

			var list =  $(this).data('list');
			if ( list ) {
				_self.removeList(list);
			}

		});

	} // initListRemove()

, removeList: function(list) {
		//console.log( list );
		$form = $('#spl-form-list-control-'+list);
		//$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'remove'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	listKey: list
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			console.log(obj);
			//$submit.button('reset');
			//$form.data('process', 'http').submit();
			$form.submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

} // removeList()

, initListCreate: function() {
		var $form = $('#spl-form-list-create');

		var $label = $('#spl-field-list-create-label');

		$form.on('submit', function(e) {

			if ( 'ajax' == $(this).data('process') ) {
				e.preventDefault();
				var label = $label.val();
				if ( label.length > 0 ) {
					_self.createList(label);
				} else {
					alert('Please add a List Name');
				}
			}

		});

	} //initListCreate

, createList: function(label) {
		var $form = $('#spl-form-list-create');
		var $submit = $('.spl-submit-list-create');
		var $hidden = $('#spl-field-list-create');

		$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'list'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	description: label
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			//console.log(obj);
			//$submit.button('reset');
			$form.data('process', 'http').submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

} //createList()

, initProfile: function() {
		this.initProfileEmail();
		this.initProfilePin();

	} // initProfile()

, initProfileEmail: function() {
		// it turns out hzws has no methods for updating email
		// will need addres ord? or maybe just select on old address?
		var $form = $('#spl-form-profile-email');
		var $email = $('#spl-field-profile-email');
		var $submit = $('#spl-submit-profile-email');

		$form.validate();

		$form.on('submit', function(e) {
			e.preventDefault();
			if ( $form.valid() ) {
				/*
				console.log( _self.user.borrower );
				console.log( _self.user.sessionToken );
				console.log( $email.val() );
				$submit.button('loading');
				*/
			}
		});

	} // initProfileEmail()

, initProfilePin: function() {
	// note: we will need a new session token here (loginUserResetMyPin)
	var $form = $('#spl-form-profile-pin');

	var $submit = $('#spl-submit-profile-pin');

	$form.validate({
		rules: {
			pinConfirm: {
	      equalTo: "#spl-field-profile-pin-new"
	    }
  	}
	});

		$form.on('submit', function(e) {
			e.preventDefault();
			if ( $form.valid() ) {
				/*
				console.log( _self.user.borrower );
				console.log( _self.user.sessionToken );
				console.log( $email.val() );
				$submit.button('loading');
				*/
			}
		});

} // initProfilePin()

, toggleCheckboxGroup: function(all, item, scope) {
		var $selectAll;
		var $selectItem;
		if ( scope ) {
			$selectAll = $(all, scope);
			$selectItem = $(item, scope);
		} else {
			$selectAll = $(all);
			$selectItem = $(item);
		}

		$selectAll.on('change', function(e) {
			if ( $(this).is(':checked') ) {
				$selectAll.prop('checked', true);
				$selectItem.prop('checked', true);
			} else {
				$selectAll.prop('checked', false);
				$selectItem.prop('checked', false);
			}
		});
	}

};
<commit_msg>my list<commit_after>// account.js

var org = {

	// added on script load
	config: {
						endpoint: { hzws: 'http://api.spokanelibrary.org/v2/hzws/' }
					 }

	// added on authentication
	,user: {}

	// called on script load
	,init: function() {
		_self = this;

		// setup variables
		this.user = $('#spl-account-summary').data('account');
		//console.log(this.user);

		// init ui
		this.initMyAccount();

	} // init()

, initMyAccount: function() {

		this.initTabs();

		this.initCko();

		this.initHolds();

		this.initLists();

		this.initProfile();

  } // initMyAccount()

, initTabs: function() {
		// apply hash onload
		if ( window.location.hash ) {
			$('a[href="'+window.location.hash+'"]').tab('show')
		}

		// apply hashchange on tab event
		//$('body').on('click', '[data-toggle="tab"]', function(e) {
		$('[data-toggle="tab"]').click(function(e) {
			window.location.hash = $(this).attr('href');
		});

	} // initTabs()

, initCko: function() {
		var $form = $('#spl-form-cko');

		this.toggleCheckboxGroup('.spl-field-cko-select-all'
														,'.spl-field-cko-select-item');

		$form.on('submit', function(e) {
			if ( 'ajax' == $(this).data('process') ) {
				e.preventDefault();

				var barcodes = new Array;
				$('.spl-field-cko-select-item:checked').each(function() {
					barcodes.push( $(this).data('barcode') );
				});

				if ( barcodes.length > 0 ) {
					_self.ckoRenew(barcodes);
				} else {
					alert('Please select item(s) to renew.');
				}
			}

		});

	} // initCko()

, ckoRenew: function(barcodes) {
		var $form = $('#spl-form-cko');
		var $submit = $('.spl-submit-cko');
		var $hidden = $('#spl-field-cko-renewal');

		$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'renew' //'renew' //'trace'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	barcodes: barcodes
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			$hidden.val(JSON.stringify(obj));
			$form.data('process', 'http').submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

	} // ckoRenew()

, initHolds: function() {
		this.initHoldsReady();
		this.initHoldsPending();

	} // initHolds()

, initHoldsReady: function() {

		var $cancel = $('.spl-field-cancel-hold-ready');

		$cancel.on('click', function(e) {
			var holdkey = $(this).data('holdkey');
			if ( holdkey ) {
				$cancel.button('loading');
				_self.cancelHold(holdkey);
			}
		});
}

, cancelHold: function(holdKey) {
		//console.log( holdKey );
		var $form = $('#spl-form-holds-ready');

		$.ajax({
	    url: this.config.endpoint.hzws+'cancel'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	holdKey: holdKey
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			//console.log(obj);
			//$submit.button('reset');
			//$form.data('process', 'http').submit();
			$form.submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

}

, initHoldsPending: function() {

		this.initHoldsPendingSuspend();

		var $form = $('#spl-form-holds-pending');

		this.toggleCheckboxGroup('.spl-field-holds-pending-select-all'
														,'.spl-field-holds-pending-select-item');

		$('.spl-field-holds-pending-control').click(function(e) {
			$form.data('action', $(this).data('action')).submit();
		});

		$form.on('submit', function(e) {
			if ( 'ajax' == $(this).data('process') ) {
				e.preventDefault();

				var holdkeys = new Array;
				$('.spl-field-holds-pending-select-item:checked').each(function() {
					holdkeys.push( $(this).data('holdkey') );
				});

				if ( holdkeys.length > 0 ) {
					_self.holdsPendingUpdate($(this).data('action'), holdkeys);
					$(this).data('action', '');
				} else {
					alert('Please select hold requests(s).');
				}

			}

		});

	} // initHoldsPending()

, initHoldsPendingSuspend: function() {
		var $suspend = $('.spl-field-holds-pending-suspend');
		var $date = $('.spl-field-holds-pending-date');
		$date.on('change', function(e) {
			if ( $(this).val().length > 0 ) {
				$suspend.attr('disabled', false);
			} else {
				$suspend.attr('disabled', true);
			}
		});
		// disallow dates in the past
		$date.datepicker({startDate: new Date() });

} // initHoldsPendingSuspend()

, holdsPendingUpdate: function(action, holdkeys) {
		var $form = $('#spl-form-holds-pending');
		var $submit = $('.spl-field-holds-pending-control');
		var $hidden = $('#spl-field-holds-pending-update');

		var $location = $('.spl-field-holds-pending-location');
		var $date = $('.spl-field-holds-pending-date');

		$submit.button('loading'); //$submit.button('reset');

		var data = { params: {sessionToken: this.user.sessionToken
	    									,	holdKeys: holdkeys
	    									}
	    					}

		var endpoint;

		switch ( action ) {
			case 'cancel':
				endpoint = 'cancel';
				break;
			case 'pickup':
				endpoint = 'pickup';
				data.params.newLocation = $location.val();
				break;
			case 'suspend':
				endpoint = 'suspend';
				data.params.suspendEndDate = $date.val();
				break;
			case 'resume':
				endpoint = 'resume';
				break;
			default:
				endpoint = null;
				break;
		}

		if ( null != typeof(endpoint) ) {
			$.ajax({
		    url: this.config.endpoint.hzws+endpoint
	    , data: data
		  })
		  .done(function(obj) {
		  	// pass results through
				$hidden.val(JSON.stringify(obj));
				//console.log(obj);
				//$submit.button('reset');
				$form.data('process', 'http').submit();
		  })
		  .fail(function() {
		  })
		  .always(function() {
		  });
		}

	} // holdsPendingUpdate()

, initLists: function() {

		this.initListToggle();
		this.initListControl();
		this.initListCreate();
		//this.initListRemove();
		//this.initListMove();

	} // initLists()

, initListControl: function() {

		$('.spl-field-list-control').click(function(e) {
			var list = $(this).data('list');
			var action = $(this).data('action');

			var form = '#spl-form-list-control-'+list;
			var $form = $(form);

			var titlekeys = new Array;
			$('.spl-field-list-select-item:checked', form).each(function() {
				titlekeys.push( $(this).data('titlekey') );
			});

			console.log( $form.data('process') );

			$form.on('submit', function(e) {

				if ( 'ajax' == $(this).data('process') ) {
					e.preventDefault();

					if ( titlekeys.length > 0 ) {
						_self.updateList(list, action, titlekeys);
					} else {
						alert('Please select title(s).');
					}

				}

			});

			$form.submit();

		});

	} // initListControl

, updateList: function(list, action, titlekeys) {

		console.log( list );
		console.log( action );
		console.log( titlekeys );

} // updateList

, initListToggle: function() {

		$('.spl-list-panel').on('show.bs.collapse', function() {

			console.log( '.spl-field-list-select-all, #'+$(this).attr('id') );

			_self.toggleCheckboxGroup('.spl-field-list-select-all'
															,'.spl-field-list-select-item'
															,'#'+$(this).attr('id')
															);

		});

	} // initListToggle

, initListMove: function() {
		var $move = $('.spl-field-list-move-control');

		$move.on('click', function(e) {
			e.preventDefault();
			//$move.button('loading');

			var from =  $(this).data('list');
			if ( from ) {

				var titlekeys = new Array;
				$('.spl-field-list-select-item:checked', '#spl-form-list-control-'+from).each(function() {
					titlekeys.push( $(this).data('titlekey') );
				});

				if ( titlekeys.length > 0 ) {
					//_self.holdsPendingUpdate($(this).data('action'), holdkeys);
					//$(this).data('action', '');
					var $to = $('#spl-field-list-move-to-'+from);
					var to = $to.val();
					_self.moveListItems(from, to, titlekeys);
				} else {
					alert('Please select list item(s).');
				}
			}

		});
} // initListMove()

, moveListItems: function(from, to, titleKeys) {

		$form = $('#spl-form-list-control-'+from);
		//$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'move'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	listKeyFrom: from
    									, listKeyTo: to
    									, titleKeys: titleKeys
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			console.log(obj);
			//$submit.button('reset');
			//$form.data('process', 'http').submit();
			//$form.submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

} // moveList()

, initListRemove: function() {

		var $remove = $('.spl-field-list-remove-control');

		$remove.on('click', function(e) {
			e.preventDefault();
			$remove.button('loading');

			var list =  $(this).data('list');
			if ( list ) {
				_self.removeList(list);
			}

		});

	} // initListRemove()

, removeList: function(list) {
		//console.log( list );
		$form = $('#spl-form-list-control-'+list);
		//$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'remove'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	listKey: list
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			console.log(obj);
			//$submit.button('reset');
			//$form.data('process', 'http').submit();
			$form.submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

} // removeList()

, initListCreate: function() {
		var $form = $('#spl-form-list-create');

		var $label = $('#spl-field-list-create-label');

		$form.on('submit', function(e) {

			if ( 'ajax' == $(this).data('process') ) {
				e.preventDefault();
				var label = $label.val();
				if ( label.length > 0 ) {
					_self.createList(label);
				} else {
					alert('Please add a List Name');
				}
			}

		});

	} //initListCreate

, createList: function(label) {
		var $form = $('#spl-form-list-create');
		var $submit = $('.spl-submit-list-create');
		var $hidden = $('#spl-field-list-create');

		$submit.button('loading'); //$submit.button('reset');
		$.ajax({
	    url: this.config.endpoint.hzws+'list'
    , data: { params: { sessionToken: this.user.sessionToken
    									,	description: label
    									}
    				}
	  })
	  .done(function(obj) {
	  	// pass results through
			//$hidden.val(JSON.stringify(obj));
			//console.log(obj);
			//$submit.button('reset');
			$form.data('process', 'http').submit();
	  })
	  .fail(function() {
	  })
	  .always(function() {
	  });

} //createList()

, initProfile: function() {
		this.initProfileEmail();
		this.initProfilePin();

	} // initProfile()

, initProfileEmail: function() {
		// it turns out hzws has no methods for updating email
		// will need addres ord? or maybe just select on old address?
		var $form = $('#spl-form-profile-email');
		var $email = $('#spl-field-profile-email');
		var $submit = $('#spl-submit-profile-email');

		$form.validate();

		$form.on('submit', function(e) {
			e.preventDefault();
			if ( $form.valid() ) {
				/*
				console.log( _self.user.borrower );
				console.log( _self.user.sessionToken );
				console.log( $email.val() );
				$submit.button('loading');
				*/
			}
		});

	} // initProfileEmail()

, initProfilePin: function() {
	// note: we will need a new session token here (loginUserResetMyPin)
	var $form = $('#spl-form-profile-pin');

	var $submit = $('#spl-submit-profile-pin');

	$form.validate({
		rules: {
			pinConfirm: {
	      equalTo: "#spl-field-profile-pin-new"
	    }
  	}
	});

		$form.on('submit', function(e) {
			e.preventDefault();
			if ( $form.valid() ) {
				/*
				console.log( _self.user.borrower );
				console.log( _self.user.sessionToken );
				console.log( $email.val() );
				$submit.button('loading');
				*/
			}
		});

} // initProfilePin()

, toggleCheckboxGroup: function(all, item, scope) {
		var $selectAll;
		var $selectItem;
		if ( scope ) {
			$selectAll = $(all, scope);
			$selectItem = $(item, scope);
		} else {
			$selectAll = $(all);
			$selectItem = $(item);
		}

		$selectAll.on('change', function(e) {
			if ( $(this).is(':checked') ) {
				$selectAll.prop('checked', true);
				$selectItem.prop('checked', true);
			} else {
				$selectAll.prop('checked', false);
				$selectItem.prop('checked', false);
			}
		});
	}

};
<|endoftext|>


<commit_before>(function (angular) {
    "use strict";

    var app = angular.module('ng.module.chat', [
        "mc.directive.editBox",
        "mc.module.notifications",
        "ngSanitize"
    ]);

    app.config(['$httpProvider', function ($httpProvider) {
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        $httpProvider.defaults.transformRequest = function (data) {
            var result = angular.isObject(data) && String(data) !== '[object File]' ? $.param(data) : data;

            return result;
        };
    }]);

    app.factory('ChatService', ['$http', '$rootScope', function ($http, $rootScope) {
        return {
            serviceProperty: null,
            getUrl: function () {
                return MapasCulturais.baseURL // + controllerId  + '/' + actionName
            },
            doSomething: function (param) {
                var data = {
                    prop: name
                };
                return $http.post(this.getUrl(), data).
                success(function (data, status) {
                    $rootScope.$emit('something', { message: "Something was done", data: data, status: status });
                }).
                    error(function (data, status) {
                        $rootScope.$emit('error', { message: "Cannot do something", data: data, status: status });
                    });
                },

            find: function (data) {

                var qdata = '/api/chatMessage/find?@select=id,createTimestamp,payload,user.profile.{id,name,singleUrl}&thread=EQ(' + data + ')';

                return $http.get(qdata).
                    success(function (data, status) {
                        for (var i = 0; i < data.length; i++) {
                            data[i].date = moment(data[i].createTimestamp.date).format('DD/MM/YYYY');
                            data[i].time = moment(data[i].createTimestamp.date).format('HH:mm');
                        }
                        $rootScope.$emit('something', { message: "Something was done", data: data, status: status });
                    }).
                    error(function (data, status) {
                        $rootScope.$emit('error', { message: "Cannot do something", data: data, status: status });
                    });

            },

            create: function (data) {

                var url = MapasCulturais.createUrl('chatMessage');

                return $http.post(url, {thread: data.thread, payload: data.payload}).
                    success(function (data, status) {
                        data.date = moment(data.createTimestamp.date).format('DD/MM/YYYY');
                        data.time = moment(data.createTimestamp.date).format('HH:mm');
                        $rootScope.$emit('something', { message: "Something was done", data: data, status: status });
                    }).
                    error(function (data, status) {
                        $rootScope.$emit('error', { message: "Cannot do something", data: data, status: status });
                    });
            },
        };
    }]);

    app.controller('ChatController', ['$scope', '$rootScope', '$timeout', 'ChatService', 'EditBox', function ($scope, $rootScope, $timeout, ChatService, EditBox) {
        var adjustingBoxPosition = false;
        $scope.editbox = EditBox;
        $scope.data = {
            threadId: null,
            messages: [],
            newMessage: '',
            previousMessage: {},
            spinner: false,
            sending: false,
            chatFocusTime: 60000,
            currentUserId: MapasCulturais.userProfile.id,

            apiQuery: {

            }
        };

        $scope.init = function(threadId) {
            $scope.data.threadId = threadId;
        }

        $scope.isChatClosed = function() {
            return $rootScope.closedChats && $rootScope.closedChats[$scope.data.threadId];
        }

        $scope.chatIsFocused = function () {
            $scope.data.chatFocusTime = 10000;
            setInterval(getLatestMessages, $scope.data.chatFocusTime);
        }

        var adjustBoxPosition = function () {
            setTimeout(function () {
                adjustingBoxPosition = true;
                $('#module-name-owner-button').click();
                adjustingBoxPosition = false;
            });
        };

        var getLatestMessages = function () {
            ChatService.find($scope.data.threadId).success(function (data, status, headers) {
                $scope.data.messages = data;
            });
        };

        // Verifica se pressionou CTRl+ENTER
        $scope.handleCtrlEnterAction = function (e) {
            var chatMessage = this.data.newMessage;
            if (e.ctrlKey && e.key === 'Enter' && chatMessage.trim() !== '') {
                e.preventDefault();
                $scope.sendMessage(chatMessage);
            }
        };

        $scope.sendMessage = function (message) {
            if($scope.data.sending){
                return;
            }

            var newMessage = {
                thread: $scope.data.threadId,
                payload: message
            };

            $scope.data.sending = true;

            ChatService.create(newMessage).success(function (data, status, headers) {
                $scope.data.messages.push(data);
                $scope.data.newMessage = '';
                $scope.data.sending = false;
            });

            $("textarea.new-message").focus();

        }

        $rootScope.$on('repeatDone:findEntity:find-entity-module-name-owner', adjustBoxPosition);

        $scope.$watch('data.spinner', function (ov, nv) {
            if (ov && !nv)
                adjustBoxPosition();
        });

        setTimeout(() => {
            ChatService.find($scope.data.threadId).success(function (data, status, headers) {
                $scope.data.messages = data;
            });

            // @todo: Mover para local adequado
            $("textarea.new-message").each(function () {
                this.setAttribute("style", "height:" + (this.scrollHeight) + "px;overflow-y:hidden;min-height:52px");
            }).on("input", function () {
                this.style.height = "auto";
                this.style.height = (this.scrollHeight) + "px";
            });
        }, 0);

        setInterval(getLatestMessages, $scope.data.chatFocusTime);

    }]);
})(angular);<commit_msg>Checa se retorno da API no chat, devolvel todas as ultimas mensagens enviadas e seta n array as que ainda nao retornaram Ref.: #1807<commit_after>(function (angular) {
    "use strict";

    var app = angular.module('ng.module.chat', [
        "mc.directive.editBox",
        "mc.module.notifications",
        "ngSanitize"
    ]);

    app.config(['$httpProvider', function ($httpProvider) {
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
        $httpProvider.defaults.transformRequest = function (data) {
            var result = angular.isObject(data) && String(data) !== '[object File]' ? $.param(data) : data;

            return result;
        };
    }]);

    app.factory('ChatService', ['$http', '$rootScope', function ($http, $rootScope) {
        return {
            serviceProperty: null,
            getUrl: function () {
                return MapasCulturais.baseURL // + controllerId  + '/' + actionName
            },
            doSomething: function (param) {
                var data = {
                    prop: name
                };
                return $http.post(this.getUrl(), data).
                success(function (data, status) {
                    $rootScope.$emit('something', { message: "Something was done", data: data, status: status });
                }).
                    error(function (data, status) {
                        $rootScope.$emit('error', { message: "Cannot do something", data: data, status: status });
                    });
                },

            find: function (data) {

                var qdata = '/api/chatMessage/find?@select=id,createTimestamp,payload,user.profile.{id,name,singleUrl}&thread=EQ(' + data + ')';

                return $http.get(qdata).
                    success(function (data, status) {
                        for (var i = 0; i < data.length; i++) {
                            data[i].date = moment(data[i].createTimestamp.date).format('DD/MM/YYYY');
                            data[i].time = moment(data[i].createTimestamp.date).format('HH:mm');
                        }
                        $rootScope.$emit('something', { message: "Something was done", data: data, status: status });
                    }).
                    error(function (data, status) {
                        $rootScope.$emit('error', { message: "Cannot do something", data: data, status: status });
                    });

            },

            create: function (data) {

                var url = MapasCulturais.createUrl('chatMessage');

                return $http.post(url, {thread: data.thread, payload: data.payload}).
                    success(function (data, status) {
                        data.date = moment(data.createTimestamp.date).format('DD/MM/YYYY');
                        data.time = moment(data.createTimestamp.date).format('HH:mm');
                        $rootScope.$emit('something', { message: "Something was done", data: data, status: status });
                    }).
                    error(function (data, status) {
                        $rootScope.$emit('error', { message: "Cannot do something", data: data, status: status });
                    });
            },
        };
    }]);

    app.controller('ChatController', ['$scope', '$rootScope', '$timeout', 'ChatService', 'EditBox', function ($scope, $rootScope, $timeout, ChatService, EditBox) {
        var adjustingBoxPosition = false;
        $scope.editbox = EditBox;
        $scope.data = {
            threadId: null,
            messages: [],
            newMessage: '',
            previousMessage: {},
            spinner: false,
            sending: false,
            chatFocusTime: 60000,
            currentUserId: MapasCulturais.userProfile.id,

            apiQuery: {

            }
        };

        $scope.init = function(threadId) {
            $scope.data.threadId = threadId;
        }

        $scope.isChatClosed = function() {
            return $rootScope.closedChats && $rootScope.closedChats[$scope.data.threadId];
        }

        $scope.chatIsFocused = function () {
            $scope.data.chatFocusTime = 10000;
            setInterval(getLatestMessages, $scope.data.chatFocusTime);
        }

        var adjustBoxPosition = function () {
            setTimeout(function () {
                adjustingBoxPosition = true;
                $('#module-name-owner-button').click();
                adjustingBoxPosition = false;
            });
        };

        var getLatestMessages = function () {
            ChatService.find($scope.data.threadId).success(function (data, status, headers) {
                $scope.data.messages.forEach(function (current) {

                    var found = false;
                    data.forEach(function(returnApi){
                        if(current.id == returnApi.id){
                            found = true;
                        }
                    });

                    if(!found){
                        data.push(current);
                    }
                });

                $scope.data.messages = data;
            });
        };

        // Verifica se pressionou CTRl+ENTER
        $scope.handleCtrlEnterAction = function (e) {
            var chatMessage = this.data.newMessage;
            if (e.ctrlKey && e.key === 'Enter' && chatMessage.trim() !== '') {
                e.preventDefault();
                $scope.sendMessage(chatMessage);
            }
        };

        $scope.sendMessage = function (message) {
            if($scope.data.sending){
                return;
            }

            var newMessage = {
                thread: $scope.data.threadId,
                payload: message
            };

            $scope.data.sending = true;

            ChatService.create(newMessage).success(function (data, status, headers) {
                $scope.data.messages.push(data);
                $scope.data.newMessage = '';
                $scope.data.sending = false;
            });

            $("textarea.new-message").focus();

        }

        $rootScope.$on('repeatDone:findEntity:find-entity-module-name-owner', adjustBoxPosition);

        $scope.$watch('data.spinner', function (ov, nv) {
            if (ov && !nv)
                adjustBoxPosition();
        });

        setTimeout(() => {
            ChatService.find($scope.data.threadId).success(function (data, status, headers) {
                $scope.data.messages = data;
            });

            // @todo: Mover para local adequado
            $("textarea.new-message").each(function () {
                this.setAttribute("style", "height:" + (this.scrollHeight) + "px;overflow-y:hidden;min-height:52px");
            }).on("input", function () {
                this.style.height = "auto";
                this.style.height = (this.scrollHeight) + "px";
            });
        }, 0);

        setInterval(getLatestMessages, $scope.data.chatFocusTime);

    }]);
})(angular);<|endoftext|>


<commit_before>// account.js

var org = {

	// added on script load
	config: {}

	// added on authentication
	,user: {}

	// called on script load
	,init: function() {
		_self = this;

		// setup variables
		this.user = $('#spl-account-summary').data('account');
		//console.log(this.user);

		// init ui
		this.initMyAccount();

	} // init()

, initMyAccount: function() {

		this.initTabs();

		this.initProfile();

  } // initMyAccount()

, initTabs: function() {
		// apply hash onload
		if ( window.location.hash ) {
			$('a[href="'+window.location.hash+'"]').tab('show')
		}

		// apply hashchange on tab event
		//$('body').on('click', '[data-toggle="tab"]', function(e) {
		$('[data-toggle="tab"]').click(function(e) {
			window.location.hash = $(this).attr('href');
		});

	} // initTabs()

, initProfile: function() {
		var $form = $('#spl-form-profile-email');

		var valid = $form.validate();

		if ( valid ) {
			$form.on('submit', function(e) {
				e.preventDefault();

				var email = $('#spl-profile-email').val();
				console.log(_self.user.borrower);
				console.log(email);
			});
		}

		/*
		// this is the input field
		$('body').on('click', '#spl-update-email', function(e) {
			e.preventDefault();

			console.log( $(this).val() );
		});
		*/

}

};
<commit_msg>valid email<commit_after>// account.js

var org = {

	// added on script load
	config: {}

	// added on authentication
	,user: {}

	// called on script load
	,init: function() {
		_self = this;

		// setup variables
		this.user = $('#spl-account-summary').data('account');
		//console.log(this.user);

		// init ui
		this.initMyAccount();

	} // init()

, initMyAccount: function() {

		this.initTabs();

		this.initProfile();

  } // initMyAccount()

, initTabs: function() {
		// apply hash onload
		if ( window.location.hash ) {
			$('a[href="'+window.location.hash+'"]').tab('show')
		}

		// apply hashchange on tab event
		//$('body').on('click', '[data-toggle="tab"]', function(e) {
		$('[data-toggle="tab"]').click(function(e) {
			window.location.hash = $(this).attr('href');
		});

	} // initTabs()

, initProfile: function() {
		var $form = $('#spl-form-profile-email');

		if ( $form.validate() ) {
			$form.on('submit', function(e) {
				e.preventDefault();

				var email = $('#spl-profile-email').val();
				console.log(_self.user.borrower);
				console.log(email);
			});
		}

		/*
		// this is the input field
		$('body').on('click', '#spl-update-email', function(e) {
			e.preventDefault();

			console.log( $(this).val() );
		});
		*/

}

};
<|endoftext|>


<commit_before>/*eslint-disable*/
xdescribe("verify user can join room with no auth", function(){

  beforeEach(function (done) {
    spyOn(Router,"go");
    Router.go('/');
    Tracker.afterFlush(done);
    done();
  });

  afterEach(function(done) {
    done();
  });

  it("should navigate from the landing page, to create-room,then join created room", function () {
    expect(document.title == "Retrospectre - Home").toBe(true);
    Template.landingPage.fireEvent("click #createRoomButton");
    expect(Router.go).toHaveBeenCalledWith("Create Room");
    waitForElement("h3",function(){
      expect(document.title == "Retrospectre - Create Room").toBe(true);
      var roomNum = $("h3").html();
      roomNum = roomNum.substring(roomNum.indexOf(":")+2);
      Template.createRoom.fireEvent("click #createAndJoinRoomButton");
      expect(Router.go).toHaveBeenCalledWith("/room/"+roomNum);
      expect(document.title == "Retrospectre - The Poltergeists").toBe(true);
      expect(window.location.pathname == ("/room/"+roomNum)).toBe(true);
    });

  });

  it("should navigate from the landing page, joined room", function () {
    expect(document.title == "Retrospectre - Home").toBe(true);
    eve = {
      target:{
        roomCode:{value: "SweetDepicturingAlpaca"}
      },
      preventDefault: function(){}
    };
    Template.landingPage.fireEvent("submit .join-room",{event:eve});
    expect(Router.go).toHaveBeenCalledWith("/room/SweetDepicturingAlpaca");
    waitForElement("h1",function(){
      $("#roomCode").val("SweetDepicturingAlpaca");
      Template.landingPage.fireEvent("submit .join-room");
      expect(Router.go).toHaveBeenCalledWith("/room/SweetDepicturingAlpaca");
      expect(document.title == "Retrospectre - The Poltergeists").toBe(true);
      expect(window.location.pathname == ("/room/1")).toBe(true);
    });

  });

});

function waitForElement(selector, successCallback) {
  var checkInterval = 50;
  var timeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
  var startTime = Date.now();
  var intervalId = Meteor.setInterval(function () {
    if (Date.now() > startTime + timeoutInterval) {
      Meteor.clearInterval(intervalId);
      // Jasmine will handle the test timeout error
    } else if ($(selector).length > 0) {
      Meteor.clearInterval(intervalId);
      successCallback();
    }
  }, checkInterval);
}
<commit_msg>uncomented test<commit_after>/*eslint-disable*/
describe("verify user can join room with no auth", function(){

  beforeEach(function (done) {
    spyOn(Router,"go");
    Router.go('/');
    Tracker.afterFlush(done);
    done();
  });

  afterEach(function(done) {
    done();
  });

  it("should navigate from the landing page, to create-room,then join created room", function () {
    expect(document.title == "Retrospectre - Home").toBe(true);
    Template.landingPage.fireEvent("click #createRoomButton");
    expect(Router.go).toHaveBeenCalledWith("Create Room");
    waitForElement("h3",function(){
      expect(document.title == "Retrospectre - Create Room").toBe(true);
      var roomNum = $("h3").html();
      roomNum = roomNum.substring(roomNum.indexOf(":")+2);
      Template.createRoom.fireEvent("click #createAndJoinRoomButton");
      expect(Router.go).toHaveBeenCalledWith("/room/"+roomNum);
      expect(document.title == "Retrospectre - The Poltergeists").toBe(true);
      expect(window.location.pathname == ("/room/"+roomNum)).toBe(true);
    });

  });

  it("should navigate from the landing page, joined room", function () {
    expect(document.title == "Retrospectre - Home").toBe(true);
    eve = {
      target:{
        roomCode:{value: "SweetDepicturingAlpaca"}
      },
      preventDefault: function(){}
    };
    Template.landingPage.fireEvent("submit .join-room",{event:eve});
    expect(Router.go).toHaveBeenCalledWith("/room/SweetDepicturingAlpaca");
    waitForElement("h1",function(){
      $("#roomCode").val("SweetDepicturingAlpaca");
      Template.landingPage.fireEvent("submit .join-room");
      expect(Router.go).toHaveBeenCalledWith("/room/SweetDepicturingAlpaca");
      expect(document.title == "Retrospectre - The Poltergeists").toBe(true);
      expect(window.location.pathname == ("/room/1")).toBe(true);
    });

  });

});

function waitForElement(selector, successCallback) {
  var checkInterval = 50;
  var timeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
  var startTime = Date.now();
  var intervalId = Meteor.setInterval(function () {
    if (Date.now() > startTime + timeoutInterval) {
      Meteor.clearInterval(intervalId);
      // Jasmine will handle the test timeout error
    } else if ($(selector).length > 0) {
      Meteor.clearInterval(intervalId);
      successCallback();
    }
  }, checkInterval);
}
<|endoftext|>


<commit_before>import {
  ruleTester,
  warningFreeBasics,
} from "../../../testUtils"
import rule, { ruleName, messages } from ".."

const testRule = ruleTester(rule, ruleName)

testRule(true, tr => {
  warningFreeBasics(tr)

  tr.ok("a {} b a {}")
  tr.ok("a {} a b {}")
  tr.ok("a {} a + a {}")
  tr.ok("a {} a[foo] {}")
  tr.ok("a[foo] {} a {}", "only checks matching last compound selectors")
  tr.ok("a { b {} } c + b {}")
  tr.ok("b a {} @media print { a {} }")

  tr.notOk("b a {} a {}", {
    message: messages.rejected("a", "b a"),
    line: 1,
    column: 8,
  })
  tr.notOk("a + a {} a {}", {
    message: messages.rejected("a", "a + a"),
    line: 1,
    column: 10,
  })
  tr.notOk("b > a[foo] {} a[foo] {}", {
    message: messages.rejected("a[foo]", "b > a[foo]"),
    line: 1,
    column: 15,
  })
  tr.notOk("e > f, b + e + a {} c {} a d {} z, f + a, y {}", {
    message: messages.rejected("f + a", "b + e + a"),
    line: 1,
    column: 36,
  })
  tr.notOk("e > f, b + e + a {} c {} a d {} z, f + a, y {}", {
    message: messages.rejected("f + a", "b + e + a"),
    line: 1,
    column: 36,
  })
  tr.notOk("a { & > b {} } b {}", {
    message: messages.rejected("b", "a > b"),
    line: 1,
    column: 16,
  })
  tr.notOk("b a {} @media print { #c a {} a {} }", {
    message: messages.rejected("a", "#c a"),
    line: 1,
    column: 31,
  })
})
<commit_msg>Add tests for pseudos<commit_after>import {
  ruleTester,
  warningFreeBasics,
} from "../../../testUtils"
import rule, { ruleName, messages } from ".."

const testRule = ruleTester(rule, ruleName)

testRule(true, tr => {
  warningFreeBasics(tr)

  tr.ok("a {} b a {}")
  tr.ok("a {} a b {}")
  tr.ok("a {} a + a {}")
  tr.ok("a {} a[foo] {}")
  tr.ok("a[foo] {} a {}", "only checks matching last compound selectors")
  tr.ok("a { b {} } c + b {}")
  tr.ok("b a {} @media print { a {} }")
  tr.ok(".a:hover {} a::after {}") // fail
  tr.ok(".m:hover {} .b {}") // pass
  tr.ok(".menu:hover {} .burger {}") // fail?!

  tr.notOk("b a {} a {}", {
    message: messages.rejected("a", "b a"),
    line: 1,
    column: 8,
  })
  tr.notOk("a + a {} a {}", {
    message: messages.rejected("a", "a + a"),
    line: 1,
    column: 10,
  })
  tr.notOk("b > a[foo] {} a[foo] {}", {
    message: messages.rejected("a[foo]", "b > a[foo]"),
    line: 1,
    column: 15,
  })
  tr.notOk("e > f, b + e + a {} c {} a d {} z, f + a, y {}", {
    message: messages.rejected("f + a", "b + e + a"),
    line: 1,
    column: 36,
  })
  tr.notOk("e > f, b + e + a {} c {} a d {} z, f + a, y {}", {
    message: messages.rejected("f + a", "b + e + a"),
    line: 1,
    column: 36,
  })
  tr.notOk("a { & > b {} } b {}", {
    message: messages.rejected("b", "a > b"),
    line: 1,
    column: 16,
  })
  tr.notOk("b a {} @media print { #c a {} a {} }", {
    message: messages.rejected("a", "#c a"),
    line: 1,
    column: 31,
  })
})
<|endoftext|>


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

'use strict';

const DIR = '../';
const Info = require(DIR + 'package');

const minor = require('minor');
const place = require('place');
const rendy = require('rendy');
const shortdate = require('shortdate');

const ERROR = Error('ERROR: version is missing. release --patch|--minor|--major');

main((error) => {
    if (error)
        console.error(error.message);
});

function main(callback) {
    const history     = 'Version history\n---------------\n';
    const link        = '//github.com/coderaiser/cloudcmd/releases/tag/';
    const template    = '- *{{ date }}*, '  +
                      '**[v{{ version }}]'  +
                      '(' + link + 'v{{ version }})**\n';

    const version     = Info.version;

    cl((error, versionNew) => {
        if (error) {
            callback(error);
        } else {
            replaceVersion('README.md', version, versionNew, callback);
            replaceVersion('HELP.md', version, versionNew, function() {
                const historyNew = history + rendy(template, {
                    date    : shortdate(),
                    version : versionNew
                });

                replaceVersion('HELP.md', history, historyNew, callback);
            });
        }
    });
}

function replaceVersion(name, version, versionNew, callback) {
    place(name, version, versionNew, (error) => {
        let msg;

        if (!error)
            msg = 'done: ' + name;

        callback(error, msg);
    });
}

function cl(callback) {
    const argv = process.argv;
    const length = argv.length - 1;
    const last = process.argv[length];
    const regExp = /^--(major|minor|patch)$/;
    const [, match] = last.match(regExp) || [];

    if (!regExp.test(last))
        return callback(ERROR);

    callback(null, getVersionNew(last, match));
}

function getVersionNew(last, match) {
    if (match)
        return minor(match, Info.version);

    return last.substr(3);
}

<commit_msg>chore(release) early return<commit_after>#!/usr/bin/env node

'use strict';

const DIR = '../';
const Info = require(DIR + 'package');

const minor = require('minor');
const place = require('place');
const rendy = require('rendy');
const shortdate = require('shortdate');

const ERROR = Error('ERROR: version is missing. release --patch|--minor|--major');

main((error) => {
    if (error)
        console.error(error.message);
});

function main(callback) {
    const history     = 'Version history\n---------------\n';
    const link        = '//github.com/coderaiser/cloudcmd/releases/tag/';
    const template    = '- *{{ date }}*, '  +
                      '**[v{{ version }}]'  +
                      '(' + link + 'v{{ version }})**\n';

    const version     = Info.version;

    cl((error, versionNew) => {
        if (error)
            return callback(error);

        replaceVersion('README.md', version, versionNew, callback);
        replaceVersion('HELP.md', version, versionNew, function() {
            const historyNew = history + rendy(template, {
                date    : shortdate(),
                version : versionNew
            });

            replaceVersion('HELP.md', history, historyNew, callback);
        });
    });
}

function replaceVersion(name, version, versionNew, callback) {
    place(name, version, versionNew, (error) => {
        if (error)
            return callback(error);

        callback(null,  'done: ' + name);
    });
}

function cl(callback) {
    const argv = process.argv;
    const length = argv.length - 1;
    const last = process.argv[length];
    const regExp = /^--(major|minor|patch)$/;
    const [, match] = last.match(regExp) || [];

    if (!regExp.test(last))
        return callback(ERROR);

    callback(null, getVersionNew(last, match));
}

function getVersionNew(last, match) {
    if (match)
        return minor(match, Info.version);

    return last.substr(3);
}

<|endoftext|>


<commit_before>/* jshint esversion: 6 */
/* jshint node: true */
/* jshint sub:true */
// This code uses ES2015 syntax that requires at least Node.js v4.
// For Node.js ES2015 support details, reference http://node.green/

"use strict";
var request = require('request-promise');
var OpenT2T = require('opent2t').OpenT2T;
var accessTokenInfo = require('./common').accessTokenInfo;

/**
* This translator class implements the "Hub" interface.
*/
class Translator {
    constructor(accessToken) {
        this._accessToken = accessToken;

        this._baseUrl = "https://api.wink.com";
        this._devicesPath = '/users/me/wink_devices';
        this._oAuthPath = '/oauth2/token';

        this._name = "Wink Hub"; // TODO: Can be pulled from OpenT2T global constants. This information is not available, at least, on wink hub.
    }

    /**
     * Get the hub definition and devices
     */
    get(expand) {
        return this.getPlatforms(expand);
    }

    /**
     * Get the list of devices discovered through the hub.
     */
    getPlatforms(expand) {
        return this._makeRequest(this._devicesPath, 'GET').then((response) => {
            var devices = response.data;

            var platformPromises = [];
            devices.forEach((winkDevice) => {
                // get the opent2t schema and translator for the wink device
                var opent2tInfo = this._getOpent2tInfo(winkDevice);

                // Do not return the physical hub device, nor any devices for which there are not translators.
                // Additionally, do not return devices that have been marked as hidden by Wink (hidden_at is a number)
                // This state is used by third party devices (such as a Nest Thermostat) that were connected to a
                // Wink account and then removed.  Wink keeps the connection, but marks them as hidden.
                if ((winkDevice.model_name !== 'HUB')  &&
                    typeof opent2tInfo !== 'undefined' &&
                    (winkDevice.hidden_at == undefined || winkDevice.hidden_at == null))
                {
                    // set the opent2t info for the wink device
                    var deviceInfo = {};
                    deviceInfo.opent2t = {};
                    deviceInfo.opent2t.controlId = this._getDeviceId(winkDevice);

                    // Create a translator for this device and get the platform information, possibly expanded
                    platformPromises.push(OpenT2T.createTranslatorAsync(opent2tInfo.translator, {'deviceInfo': deviceInfo, 'hub': this})
                                    .then((translator) => {

                                        // Use get to translate the Wink formatted device that we already got in the previous request.
                                        // We already have this data, so no need to make an unnecesary request over the wire.
                                        return OpenT2T.invokeMethodAsync(translator, opent2tInfo.schema, 'get', [expand, winkDevice])
                                            .then((platformResponse) => {
                                                return platformResponse;
                                            });
                                    }));
                }
            });

             return Promise.all(platformPromises)
                    .then((platforms) => {
                        var toReturn = {};
                        toReturn.schema = "opent2t.p.hub";
                        toReturn.platforms = platforms;
                        return toReturn;
                    });
        });
    }

    /**
     * Get the name of the hub.  Ties to the n property from oic.core
     */
    getN() {
        return this._name;
    }

    /**
     * Gets device details (all fields), response formatted per http://docs.winkapiv2.apiary.io/
     */
    getDeviceDetailsAsync(deviceType, deviceId) {

        // build request URI
        var requestPath = '/' + deviceType + '/' + deviceId;

        // Make the async request
        return this._makeRequest(requestPath, 'GET');
    }

    /**
     * Puts device details (all fields) payload formatted per http://docs.winkapiv2.apiary.io/
    */
    putDeviceDetailsAsync(deviceType, deviceId, putPayload) {

        // build request path and body
        var requestPath = '/' + deviceType + '/' + deviceId;
        var putPayloadString = JSON.stringify(putPayload);

        // Make the async request
        return this._makeRequest(requestPath, 'PUT', putPayloadString);
    }

    /**
     * Refreshes the OAuth token for the hub by sending a refresh POST to the wink provider
     */
    refreshAuthToken(authInfo)
    {
        console.log("In refreshAuthToken for WINK hub..");

        if (authInfo == undefined || authInfo == null){
            throw new Error("Please provide the existing authInfo object to allow the oAuth token to be refreshed");
        }

        // POST oauth2/token
        var postPayloadString = JSON.stringify({
            'client_id': authInfo[1].client_id,
            'client_secret': authInfo[1].client_secret,
            'grant_type': 'refresh_token',
            'refresh_token': this._accessToken.refreshToken,
        });

        return this._makeRequest(this._oAuthPath, "POST", postPayloadString, false).then((body)=>
        {
            // _makeRequest() already returns a JSON representation of the POST response body
            // return the auth properties out in our own response back
             return new accessTokenInfo(
                    body.access_token,
                    body.refresh_token,
                    body.token_type,
                    body.scopes
                );
            // there isn't a 'scopes' property returned as a result of this request according to
            // http://docs.wink.apiary.io/#reference/oauth/obtain-access-token/sign-in-as-user,-or-refresh-user's-expired-access-token
            // so am assuming the caller of this API will expect nulls
        });
    }

    /**
     * Subscribes to a Wink pubsubhubbub feed.  This function is designed to be called twice.
     * The first call will contain just the callbackUrl, which will be subscribed for postbacks
     * from the Wink cloud service.
     * The second call will contain the request, and response for a GET to the callbackUrl that was
     * provided in order to complete the verification of the subscription.
     *
     * The expected sequence is as follows:
     * - Call subscribe with a callback url, get a response.
     * - Http server running at the callback url receives a GET request, this request and a response string
     *      are passed to a 2nd call to subscribe() (callbackUrl is ignored in this case)
     * - Http server responds to the GET with the verificationResponseContent that was populated by subscribe
     * - Future POST calls to the callback url are passed to the device translator get*Uri function
     *
     * All calls to subscribe will return the expiration time of the subscription.  For Wink, this is
     * currently 24 hours.  Subsequent calls to subscribe will refresh the expiration time, and in the
     * case of Wink/Pubsubhubbub will not require verification.
     *
     * @param {string} deviceType - Device Type (e.g. 'thermostats')
     * @param {string|number} deviceId - Id for the specific device
     * @param {string} callbackUrl - Callback url for feed postbacks
     * @param {HttpRequest} verificationRequest - GET request received by the server at a previously subscribed
     *      callback URL.  This completes the verification half of the subscription.
     * @param {HttpResponse} verificationResponseContent - Content that should be provided in the response to the
     *      verification request as {'Content-Type': 'text/plain'}.  All responses should use code 200 unless an
     *      error is caught.
     * @returns {number} Object containing the subscription expiration time, and any content that
     *      needs to be included in a response to the original request.
     */
    _subscribe(deviceType, deviceId, callbackUrl, verificationRequest) {

        if (callbackUrl) {
            // Subscribe to notifications

            var requestPath = '/' + deviceType + '/' + deviceId + '/subscriptions';

            // Winks implementation of PubSubHubbub differs from the standard in that we do not need to provide
            // the topic, or mode on this request.  Topic is implicit from the URL (deviceType/deviceId), and
            // separate requests exist for mode (subscribe and unsubscribe vis POST/DELETE).

            // Additionally, subscriptions will expire after 24 hours (for now), and need to be refreshed
            // with another call to this function.

            var postPayload = {
                callback: callbackUrl,
            }

            var postPayloadString = JSON.stringify(postPayload);

            return this._makeRequest(requestPath, 'POST', postPayloadString).then((response) => {
                // Return the expiration time for the subscription
                return {
                    expiration: response.data.expires_at,
                    response: ""
                };
            })

        } else if (verificationRequest) {
            // Verify a subscription request by constructing the appropriate response

            var params = require('url').parse(verificationRequest.url, true, true);
            // This is a subscription validation, populate the response
            switch(params.query['hub.mode']) {
                case "denied":
                    // The subscription cannot be completed, access was denied.
                    // This is likely due to a bad access token.
                    throw new Error("Access denied");
                case "unsubscribe": // Could remove this, see below.
                case "subscribe":
                    // Successful subscription/unsubscription requires a response to contain the same
                    // challenge that was included in the request.
                    // Wink doesn't actually use hub.mode unsubscribe, and instead uses DELETE to perform the action
                    // with no verification.

                    // Verify that this subscription is for the correct topic
                    if (params.query['hub.topic'].endsWith(deviceType + '/' + deviceId)) {
                        return {
                            expiration: params.query['hub.lease_seconds'],
                            response: params.query['hub.challenge']
                        }
                    } else {
                        // There is a mistmatch.  This subscription doesn't match this device.
                        throw new Error("Subscription cannot be verified");
                    }
                default:
                    // Hub mode is unknown.
                    throw new Error("Unknown request");
            }
        }
    }

    /**
     * Unsubscribes from an existing Wink pubsubhubbub feed.  The subscription id for a
     * topic is not cached, so this results in 2 web calls:
     * - GET to return a list of subscriptions
     * - DELETE to remove the existing subscriptions.
     *
     * Wink's pubsubhubbub is slightly different than standard as it uses DELETE along
     * with a subscriptionID rather than another GET with a hub.mode of 'unsubscribe'
     *
     * @param {string} deviceType - Device Type (e.g. 'thermostats')
     * @param {string|number} deviceId - Id for the specific device
     * @param {string} callbackUrl - URL that will be unsubscribed
     * @returns {request} Promise that supplies the server response
     *
     */
    _unsubscribe(deviceType, deviceId, callbackUrl)
    {
        // Find the subscription ID for this callback URL
        return this._getSubscriptions(deviceType, deviceId).then((subscriptions) => {
            subscriptions.forEach((sub) => {
                // Find the subscription id for this callback URL and device
                if (sub.callback == callbackUrl &&
                    sub.topic.endsWith(deviceType + '/' + deviceId)) {
                    var requestPath = '/' + deviceType + '/' + deviceId + '/subscriptions/' + sub.subscription_id;

                    // Do the actual unsubscribe
                    return this._makeRequest(requestPath, 'DELETE').then(() => {
                        return {};
                    })
                }
            });

            // Subscription wasn't found so there's nothing to do
            return {};
        });
    }

    /**
     * Gets all subscriptions for a device.
     *
     * @param {string} deviceType - Device Type (e.g. 'thermostats')
     * @param {string|number} deviceId - Id for the specific device
     * @returns {request} Promise that supplies the list of subscriptions
     *
     * This is just a helper for tests and verification.
     */
    _getSubscriptions(deviceType, deviceId) {
        // GET /<deviceType>/<device Id>/subscriptions
        var requestPath = '/' + deviceType + '/' + deviceId + '/subscriptions';

        return this._makeRequest(requestPath, 'GET').then((response) => {
            return response.data;
        });
    }

    /**
     * Given the hub specific device, returns the opent2t schema and translator
    */
    _getOpent2tInfo(winkDevice) {
        if (winkDevice.thermostat_id) {
            return {
                "schema": 'opent2t.p.thermostat',
                "translator": "opent2t-translator-com-wink-thermostat"
            };
        }
        else if (winkDevice.binary_switch_id) {
            return {
                "schema": 'opent2t.p.switch.binary',
                "translator": "opent2t-translator-com-wink-binaryswitch"
            };
        }
        else if (winkDevice.light_bulb_id) {
            return {
                "schema": 'opent2t.p.light',
                "translator": "opent2t-translator-com-wink-lightbulb"
            };
        }

        return undefined;
    }

    /**
     * Internal helper method which makes the actual request to the wink service
     */
    _makeRequest(path, method, content, includeBearerHeader) {

        var addBearerHeader = (typeof includeBearerHeader !== 'undefined') ?  includeBearerHeader : true;

        // build request URI
        var requestUri = this._baseUrl + path;

        var headers = [];

        // Set the headers
        if (addBearerHeader) {
            headers['Authorization'] = 'Bearer ' + this._accessToken.accessToken;
            headers['Accept'] = 'application/json';
        }

        if (content) {
            headers['Content-Type'] = 'application/json';
            headers['Content-length'] = content.length;
        }

        var options = {
            url: requestUri,
            method: method,
            headers: headers,
            followAllRedirects: true
        };

        if (content) {
            options.body = content;
        }

        // Start the async request
        return request(options)
            .then(function (body) {
                return JSON.parse(body);
            })
            .catch(function (err) {
                console.log("Request failed to: " + options.method + " - " + options.url);
                console.log("Error            : " + err.statusCode + " - " + err.response.statusMessage);
                // todo auto refresh in specific cases, issue 74
                throw err;
            });
    }

    /**
     * Wink has a semi-complicated way of getting the actual device id of a device.
     */
    _getDeviceId(winkDevice) {
        // get the device id
        var deviceId = winkDevice.light_bulb_id ||
            winkDevice.air_conditioner_id ||
            winkDevice.binary_switch_id ||
            winkDevice.shade_id ||
            winkDevice.camera_id ||
            winkDevice.doorbell_id ||
            winkDevice.eggtray_id ||
            winkDevice.garage_door_id ||
            winkDevice.cloud_clock_id ||
            winkDevice.lock_id ||
            winkDevice.dial_id ||
            winkDevice.alarm_id ||
            winkDevice.power_strip_id ||
            winkDevice.outlet_id ||
            winkDevice.piggy_bank_id ||
            winkDevice.deposit_id ||
            winkDevice.refrigerator_id ||
            winkDevice.propane_tank_id ||
            winkDevice.remote_id ||
            winkDevice.sensor_pod_id ||
            winkDevice.siren_id ||
            winkDevice.smoke_detector_id ||
            winkDevice.sprinkler_id ||
            winkDevice.thermostat_id ||
            winkDevice.water_heater_id ||
            winkDevice.scene_id ||
            winkDevice.condition_id ||
            winkDevice.robot_id;

        return deviceId;
    }
}

module.exports = Translator;<commit_msg>Code review comments<commit_after>/* jshint esversion: 6 */
/* jshint node: true */
/* jshint sub:true */
// This code uses ES2015 syntax that requires at least Node.js v4.
// For Node.js ES2015 support details, reference http://node.green/

"use strict";
var request = require('request-promise');
var OpenT2T = require('opent2t').OpenT2T;
var accessTokenInfo = require('./common').accessTokenInfo;

/**
* This translator class implements the "Hub" interface.
*/
class Translator {
    constructor(accessToken) {
        this._accessToken = accessToken;

        this._baseUrl = "https://api.wink.com";
        this._devicesPath = '/users/me/wink_devices';
        this._oAuthPath = '/oauth2/token';

        this._name = "Wink Hub"; // TODO: Can be pulled from OpenT2T global constants. This information is not available, at least, on wink hub.
    }

    /**
     * Get the hub definition and devices
     */
    get(expand) {
        return this.getPlatforms(expand);
    }

    /**
     * Get the list of devices discovered through the hub.
     */
    getPlatforms(expand) {
        return this._makeRequest(this._devicesPath, 'GET').then((response) => {
            var devices = response.data;

            var platformPromises = [];
            devices.forEach((winkDevice) => {
                // get the opent2t schema and translator for the wink device
                var opent2tInfo = this._getOpent2tInfo(winkDevice);

                // Do not return the physical hub device, nor any devices for which there are not translators.
                // Additionally, do not return devices that have been marked as hidden by Wink (hidden_at is a number)
                // This state is used by third party devices (such as a Nest Thermostat) that were connected to a
                // Wink account and then removed.  Wink keeps the connection, but marks them as hidden.
                if ((winkDevice.model_name !== 'HUB')  &&
                    typeof opent2tInfo !== 'undefined' &&
                    (winkDevice.hidden_at == undefined || winkDevice.hidden_at == null))
                {
                    // set the opent2t info for the wink device
                    var deviceInfo = {};
                    deviceInfo.opent2t = {};
                    deviceInfo.opent2t.controlId = this._getDeviceId(winkDevice);

                    // Create a translator for this device and get the platform information, possibly expanded
                    platformPromises.push(OpenT2T.createTranslatorAsync(opent2tInfo.translator, {'deviceInfo': deviceInfo, 'hub': this})
                                    .then((translator) => {

                                        // Use get to translate the Wink formatted device that we already got in the previous request.
                                        // We already have this data, so no need to make an unnecesary request over the wire.
                                        return OpenT2T.invokeMethodAsync(translator, opent2tInfo.schema, 'get', [expand, winkDevice])
                                            .then((platformResponse) => {
                                                return platformResponse;
                                            });
                                    }));
                }
            });

             return Promise.all(platformPromises)
                    .then((platforms) => {
                        var toReturn = {};
                        toReturn.schema = "opent2t.p.hub";
                        toReturn.platforms = platforms;
                        return toReturn;
                    });
        });
    }

    /**
     * Get the name of the hub.  Ties to the n property from oic.core
     */
    getN() {
        return this._name;
    }

    /**
     * Gets device details (all fields), response formatted per http://docs.winkapiv2.apiary.io/
     */
    getDeviceDetailsAsync(deviceType, deviceId) {

        // build request URI
        var requestPath = '/' + deviceType + '/' + deviceId;

        // Make the async request
        return this._makeRequest(requestPath, 'GET');
    }

    /**
     * Puts device details (all fields) payload formatted per http://docs.winkapiv2.apiary.io/
    */
    putDeviceDetailsAsync(deviceType, deviceId, putPayload) {

        // build request path and body
        var requestPath = '/' + deviceType + '/' + deviceId;
        var putPayloadString = JSON.stringify(putPayload);

        // Make the async request
        return this._makeRequest(requestPath, 'PUT', putPayloadString);
    }

    /**
     * Refreshes the OAuth token for the hub by sending a refresh POST to the wink provider
     */
    refreshAuthToken(authInfo)
    {
        var invalidAuthErrorMessage = "Invalid authInfo object.Please provide the existing authInfo object  with clientId + client_secret to allow the oAuth token to be refreshed";

        if (authInfo == undefined || authInfo == null){
            throw new Error(invalidAuthErrorMessage);
        }

        if (authInfo.length !== 2)
        {
            // We expect the original authInfo object used in the onboarding flow
            // not defining a brand new type for the Refresh() contract and re-using
            // what is defined for Onboarding()
            throw new Error(invalidAuthErrorMessage);
        }

        // POST oauth2/token
        var postPayloadString = JSON.stringify({
            'client_id': authInfo[1].client_id,
            'client_secret': authInfo[1].client_secret,
            'grant_type': 'refresh_token',
            'refresh_token': this._accessToken.refreshToken,
        });

        return this._makeRequest(this._oAuthPath, "POST", postPayloadString, false).then((body)=>
        {
            // _makeRequest() already returns a JSON representation of the POST response body
            // return the auth properties out in our own response back
             return new accessTokenInfo(
                    body.access_token,
                    body.refresh_token,
                    body.token_type,
                    body.scopes
                );
            // there isn't a 'scopes' property returned as a result of this request according to
            // http://docs.wink.apiary.io/#reference/oauth/obtain-access-token/sign-in-as-user,-or-refresh-user's-expired-access-token
            // so am assuming the caller of this API will expect nulls
        });
    }

    /**
     * Subscribes to a Wink pubsubhubbub feed.  This function is designed to be called twice.
     * The first call will contain just the callbackUrl, which will be subscribed for postbacks
     * from the Wink cloud service.
     * The second call will contain the request, and response for a GET to the callbackUrl that was
     * provided in order to complete the verification of the subscription.
     *
     * The expected sequence is as follows:
     * - Call subscribe with a callback url, get a response.
     * - Http server running at the callback url receives a GET request, this request and a response string
     *      are passed to a 2nd call to subscribe() (callbackUrl is ignored in this case)
     * - Http server responds to the GET with the verificationResponseContent that was populated by subscribe
     * - Future POST calls to the callback url are passed to the device translator get*Uri function
     *
     * All calls to subscribe will return the expiration time of the subscription.  For Wink, this is
     * currently 24 hours.  Subsequent calls to subscribe will refresh the expiration time, and in the
     * case of Wink/Pubsubhubbub will not require verification.
     *
     * @param {string} deviceType - Device Type (e.g. 'thermostats')
     * @param {string|number} deviceId - Id for the specific device
     * @param {string} callbackUrl - Callback url for feed postbacks
     * @param {HttpRequest} verificationRequest - GET request received by the server at a previously subscribed
     *      callback URL.  This completes the verification half of the subscription.
     * @param {HttpResponse} verificationResponseContent - Content that should be provided in the response to the
     *      verification request as {'Content-Type': 'text/plain'}.  All responses should use code 200 unless an
     *      error is caught.
     * @returns {number} Object containing the subscription expiration time, and any content that
     *      needs to be included in a response to the original request.
     */
    _subscribe(deviceType, deviceId, callbackUrl, verificationRequest) {

        if (callbackUrl) {
            // Subscribe to notifications

            var requestPath = '/' + deviceType + '/' + deviceId + '/subscriptions';

            // Winks implementation of PubSubHubbub differs from the standard in that we do not need to provide
            // the topic, or mode on this request.  Topic is implicit from the URL (deviceType/deviceId), and
            // separate requests exist for mode (subscribe and unsubscribe vis POST/DELETE).

            // Additionally, subscriptions will expire after 24 hours (for now), and need to be refreshed
            // with another call to this function.

            var postPayload = {
                callback: callbackUrl,
            }

            var postPayloadString = JSON.stringify(postPayload);

            return this._makeRequest(requestPath, 'POST', postPayloadString).then((response) => {
                // Return the expiration time for the subscription
                return {
                    expiration: response.data.expires_at,
                    response: ""
                };
            })

        } else if (verificationRequest) {
            // Verify a subscription request by constructing the appropriate response

            var params = require('url').parse(verificationRequest.url, true, true);
            // This is a subscription validation, populate the response
            switch(params.query['hub.mode']) {
                case "denied":
                    // The subscription cannot be completed, access was denied.
                    // This is likely due to a bad access token.
                    throw new Error("Access denied");
                case "unsubscribe": // Could remove this, see below.
                case "subscribe":
                    // Successful subscription/unsubscription requires a response to contain the same
                    // challenge that was included in the request.
                    // Wink doesn't actually use hub.mode unsubscribe, and instead uses DELETE to perform the action
                    // with no verification.

                    // Verify that this subscription is for the correct topic
                    if (params.query['hub.topic'].endsWith(deviceType + '/' + deviceId)) {
                        return {
                            expiration: params.query['hub.lease_seconds'],
                            response: params.query['hub.challenge']
                        }
                    } else {
                        // There is a mistmatch.  This subscription doesn't match this device.
                        throw new Error("Subscription cannot be verified");
                    }
                default:
                    // Hub mode is unknown.
                    throw new Error("Unknown request");
            }
        }
    }

    /**
     * Unsubscribes from an existing Wink pubsubhubbub feed.  The subscription id for a
     * topic is not cached, so this results in 2 web calls:
     * - GET to return a list of subscriptions
     * - DELETE to remove the existing subscriptions.
     *
     * Wink's pubsubhubbub is slightly different than standard as it uses DELETE along
     * with a subscriptionID rather than another GET with a hub.mode of 'unsubscribe'
     *
     * @param {string} deviceType - Device Type (e.g. 'thermostats')
     * @param {string|number} deviceId - Id for the specific device
     * @param {string} callbackUrl - URL that will be unsubscribed
     * @returns {request} Promise that supplies the server response
     *
     */
    _unsubscribe(deviceType, deviceId, callbackUrl)
    {
        // Find the subscription ID for this callback URL
        return this._getSubscriptions(deviceType, deviceId).then((subscriptions) => {
            subscriptions.forEach((sub) => {
                // Find the subscription id for this callback URL and device
                if (sub.callback == callbackUrl &&
                    sub.topic.endsWith(deviceType + '/' + deviceId)) {
                    var requestPath = '/' + deviceType + '/' + deviceId + '/subscriptions/' + sub.subscription_id;

                    // Do the actual unsubscribe
                    return this._makeRequest(requestPath, 'DELETE').then(() => {
                        return {};
                    })
                }
            });

            // Subscription wasn't found so there's nothing to do
            return {};
        });
    }

    /**
     * Gets all subscriptions for a device.
     *
     * @param {string} deviceType - Device Type (e.g. 'thermostats')
     * @param {string|number} deviceId - Id for the specific device
     * @returns {request} Promise that supplies the list of subscriptions
     *
     * This is just a helper for tests and verification.
     */
    _getSubscriptions(deviceType, deviceId) {
        // GET /<deviceType>/<device Id>/subscriptions
        var requestPath = '/' + deviceType + '/' + deviceId + '/subscriptions';

        return this._makeRequest(requestPath, 'GET').then((response) => {
            return response.data;
        });
    }

    /**
     * Given the hub specific device, returns the opent2t schema and translator
    */
    _getOpent2tInfo(winkDevice) {
        if (winkDevice.thermostat_id) {
            return {
                "schema": 'opent2t.p.thermostat',
                "translator": "opent2t-translator-com-wink-thermostat"
            };
        }
        else if (winkDevice.binary_switch_id) {
            return {
                "schema": 'opent2t.p.switch.binary',
                "translator": "opent2t-translator-com-wink-binaryswitch"
            };
        }
        else if (winkDevice.light_bulb_id) {
            return {
                "schema": 'opent2t.p.light',
                "translator": "opent2t-translator-com-wink-lightbulb"
            };
        }

        return undefined;
    }

    /**
     * Internal helper method which makes the actual request to the wink service
     */
    _makeRequest(path, method, content, includeBearerHeader) {

        var addBearerHeader = (typeof includeBearerHeader !== 'undefined') ?  includeBearerHeader : true;

        // build request URI
        var requestUri = this._baseUrl + path;

        var headers = [];

        // Set the headers
        if (addBearerHeader) {
            headers['Authorization'] = 'Bearer ' + this._accessToken.accessToken;
            headers['Accept'] = 'application/json';
        }

        if (content) {
            headers['Content-Type'] = 'application/json';
            headers['Content-length'] = content.length;
        }

        var options = {
            url: requestUri,
            method: method,
            headers: headers,
            followAllRedirects: true
        };

        if (content) {
            options.body = content;
        }

        // Start the async request
        return request(options)
            .then(function (body) {
                return JSON.parse(body);
            })
            .catch(function (err) {
                console.log("Request failed to: " + options.method + " - " + options.url);
                console.log("Error            : " + err.statusCode + " - " + err.response.statusMessage);
                throw err;
            });
    }

    /**
     * Wink has a semi-complicated way of getting the actual device id of a device.
     */
    _getDeviceId(winkDevice) {
        // get the device id
        var deviceId = winkDevice.light_bulb_id ||
            winkDevice.air_conditioner_id ||
            winkDevice.binary_switch_id ||
            winkDevice.shade_id ||
            winkDevice.camera_id ||
            winkDevice.doorbell_id ||
            winkDevice.eggtray_id ||
            winkDevice.garage_door_id ||
            winkDevice.cloud_clock_id ||
            winkDevice.lock_id ||
            winkDevice.dial_id ||
            winkDevice.alarm_id ||
            winkDevice.power_strip_id ||
            winkDevice.outlet_id ||
            winkDevice.piggy_bank_id ||
            winkDevice.deposit_id ||
            winkDevice.refrigerator_id ||
            winkDevice.propane_tank_id ||
            winkDevice.remote_id ||
            winkDevice.sensor_pod_id ||
            winkDevice.siren_id ||
            winkDevice.smoke_detector_id ||
            winkDevice.sprinkler_id ||
            winkDevice.thermostat_id ||
            winkDevice.water_heater_id ||
            winkDevice.scene_id ||
            winkDevice.condition_id ||
            winkDevice.robot_id;

        return deviceId;
    }
}

module.exports = Translator;<|endoftext|>