<commit_before>/**
  Request Builder Pipeline Functions
  @description a collection of functions intended to be ran in parallel in order
    to create the parts to a request from the action data & global config
  @exports {function} cacheLookup
  @exports {function} buildHeaders
  @exports {function} parseBody
  @exports {function} makeStatusActions
  @exports {function} makeRemoteCallHooks

  @todo there's a whole lot of repeating myself for handling hook types
    (i.e., dispatch if primitive or object, call if fn, resolve if promise)
    maybe that logic can be pulled out somewhere?
**/

import { APIError, CallProcessingError } from './errors';
import { isCacheableRequest, isPromise } from './utils';

/**
  @private
  @name cacheLookup
  @desc consults the given state cache mapping to see if we already have a value
    note that this will *not* run when using actionable HTTP methods like POST
  @param {string} method HTTP verb, used to determine if we should cache
  @param {function} cacheMapping
  @param {boolean} nocache
  @param {function} emitSuccessOnCacheHit
  @returns {Promise<boolean>} result

  @TODO allow `cacheMapping` to return a value that will be supplied to the
    success event if `emitSuccessOnCacheHit` is true after the whole thing
    short circuits
**/
export function cacheLookup(state, { method, cacheMapping, nocache }) {
  const stepName = '#cacheLookup';
  if (!isCacheableRequest(method) || !cacheMapping || nocache)
    return Promise.resolve(false);

  try {
    return Promise.resolve(cacheMapping(state));
  } catch (error) {
    const msg = 'Error thrown while evaluating cache mapping';
    return Promise.reject(new CallProcessingError(msg, stepName, error));
  }
}

/**
  @private
  @name buildHeaders
  @desc builds a flat header map by evaluating each k/v in the given headers
    object, optionally injecting any headers from the top level configuration,
    will evaluate promises & functions passing in the state.
    If a header val is a function that returns false the header will be skipped
    NOTE: headers given in the action *will* overwrite globally injected headers
  @param {object} state
  @param {object} headers flat map w/ a mix of values, functions, or promises
  @param {object|string} body used to determine if we'll be sending JSON
  @param {object} injectedHeaders optional
  @returns {Promise<object|CallProcessingError}
**/
export function buildHeaders(state, { headers, body }, injectedHeaders) {
  const stepName = '#buildHeaders';
  let headerMap = Object.assign({}, injectedHeaders || {}, headers);

  if (
    body &&
    typeof body === 'object' &&
    !headerMap.hasOwnProperty('Content-Type')
  ) headerMap['Content-Type'] = 'application/json';

  let ps = Object.keys(headerMap).map(header => {
    const headerVal = headerMap[header];

    if (typeof headerVal === 'function')
      try {
        return { key: header, value: headerVal(state) };
      } catch (error) {
        const msg = `Error thrown when evaluating header function ${header}`;
        return Promise.reject(new CallProcessingError(msg, stepName, error));
      }
    else if (isPromise(headerVal))
      return headerVal.then(v => ({ key: header, value: v }));
    // if you're not a promise but still an object we don't want you
    else if (typeof headerVal === 'object')
      return Promise.reject(
        new CallProcessingError(`Object given for header ${header}`, stepName)
      );
    // I guess it's just a plain value
    else return { key: header, value: headerVal };
  });

  return Promise.all(ps).then(p => p
    .filter(pair => pair.value !== false)
    .reduce((acc, pair) => { acc[pair.key] = pair.value; return acc; }, {})
  );
}

/**
  @private
  @name parseBody
  @desc detects if the request body is an object, if so will attempt to parse
    it as JSON, otherwise just returning it; if body is a function it will be
    called with the state passed in - supports promises
  @param {object} state
  @param {any} body
  @returns {Promise<string>} parsedBody
**/
export function parseBody(state, body) {
  const stepName = '#parseBody';
  if (!body) return Promise.resolve(null);
  else if (isPromise(body))
    return body.then(realBody => parseBody(state, realBody));
  else if (typeof body === 'object')
    try {
      return Promise.resolve(JSON.stringify(body));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error parsing body', stepName, error
      ));
    }
  else if (typeof body === 'function')
    try {
      return Promise.resolve(body(state));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error occurred while evaluating body function', stepName, error
      ));
    }
  else return Promise.resolve(body);
}

/**
  @private
  @name makeStatusActions
  @desc parses the status action configuration into actionable functions
  @param {object} ogActions original status action configuration, keys
    are codes & values are a mix of values, functions, promises, or objects
  @param {function} dispatch
  @returns {Promise<object>} statusActions where keys are codes & values
    are callbacks
**/
export function makeStatusActions(ogActions, dispatch) {
  const makeCallbackFromValue = val => {
    // functions are called w/ the response object
    if (typeof val === 'function')
      return Promise.resolve(res => val(dispatch, res));
    else if (isPromise(val))
      return val.then(newVal => makeCallbackFromValue(newVal));
    else if (typeof val === 'object')
      return Promise.resolve(() => dispatch(val));
    return Promise.resolve(() => dispatch({ type: val }));
  };

  let ps = Object.keys(ogActions)
  .map(code => makeCallbackFromValue(ogActions[code])
    .then(callback => ({ code, callback }))
  );

  return Promise.all(ps).then(entries => entries
    .reduce((acc, entry) => {
      acc[entry.code] = entry.callback;
      return acc;
    }, {})
  );
}

/**
  @private
  @name makeRemoteCallHooks
  @desc parses the given lifecycle hooks, providing redirections for the
    remote call API and giving the correct args for each hook
  @param {object} lifecycleHooks contains the orginal user lifecycle hooks
  @param {any} lifecycleHooks.request
  @param {any} lifecycleHooks.success
  @param {any} lifecycleHooks.failure
  @param {function} dispatch
  @returns {Promise<object>} remoteCallHooks
**/
export function makeRemoteCallHooks(lifecycleHooks, dispatch) {
  const stepName = '#makeRemoteCallHooks';

  // ensure that for each hook we can call a callback on fn values so
  // that we can provide the set of arguments for that hook
  const redirectHook = (hookVal, cb) => {
    if (!hookVal)
      // if hook is left out just make the call hook a noop
      return Promise.resolve(() => {});
    else if (typeof hookVal === 'function')
      return Promise.resolve(cb(hookVal));
    else if (isPromise(hookVal))
      return hookVal.then(v => redirectHook(v, cb));
    else if (typeof hookVal === 'object')
      return Promise.resolve(() => dispatch(hookVal));
    return Promise.resolve(() => dispatch({ type: hookVal }));
  };

  const reqCb = fn => () => fn(dispatch);
  const sucCb = fn => (data, res) => fn(data, dispatch, res);
  const errCb = fn => (status, data, res) => {
    const msg = data.error || data.err || res.statusText;
    fn(new APIError(status, msg, res), dispatch, data, res);
  };

  return Promise.all([
    redirectHook(lifecycleHooks.request, reqCb),
    redirectHook(lifecycleHooks.success, sucCb),
    redirectHook(lifecycleHooks.failure, errCb)
  ]).then(([ onBeforeCall, onCallSuccess, onCallFailure ]) =>
    ({ onBeforeCall, onCallSuccess, onCallFailure })
  ).catch(error => {
    const msg = 'Error thrown while processing lifecycle hooks';
    Promise.reject(new CallProcessingError(msg, stepName, error));
  });
}
<commit_msg>Allow cacheLookup to return a found value<commit_after>/**
  Request Builder Pipeline Functions
  @description a collection of functions intended to be ran in parallel in order
    to create the parts to a request from the action data & global config
  @exports {function} cacheLookup
  @exports {function} buildHeaders
  @exports {function} parseBody
  @exports {function} makeStatusActions
  @exports {function} makeRemoteCallHooks

  @todo there's a whole lot of repeating myself for handling hook types
    (i.e., dispatch if primitive or object, call if fn, resolve if promise)
    maybe that logic can be pulled out somewhere?
**/

import { APIError, CallProcessingError } from './errors';
import { isCacheableRequest, isPromise } from './utils';

/**
  @private
  @name cacheLookup
  @desc consults the given state cache mapping to see if we already have a value
    note that this will *not* run when using actionable HTTP methods like POST
  @param {string} method HTTP verb, used to determine if we should cache
  @param {function} cacheMapping
  @param {boolean} nocache
  @param {function} emitSuccessOnCacheHit
  @returns {Promise<boolean>} result

  @TODO allow `cacheMapping` to return a value that will be supplied to the
    success event if `emitSuccessOnCacheHit` is true after the whole thing
    short circuits
**/
export function cacheLookup(state, {
  method, cacheMapping, nocache, emitSuccessOnCacheHit
}) {
  const stepName = '#cacheLookup';
  if (!isCacheableRequest(method) || !cacheMapping || nocache)
    return Promise.resolve(false);

  try {
    return Promise.resolve(cacheMapping(state))
    .then(foundValue => {
      if (emitSuccessOnCacheHit) return foundValue;

      // TODO: use a Symbol here in case users want to actually use false here
      return false;
    });
  } catch (error) {
    const msg = 'Error thrown while evaluating cache mapping';
    return Promise.reject(new CallProcessingError(msg, stepName, error));
  }
}

/**
  @private
  @name buildHeaders
  @desc builds a flat header map by evaluating each k/v in the given headers
    object, optionally injecting any headers from the top level configuration,
    will evaluate promises & functions passing in the state.
    If a header val is a function that returns false the header will be skipped
    NOTE: headers given in the action *will* overwrite globally injected headers
  @param {object} state
  @param {object} headers flat map w/ a mix of values, functions, or promises
  @param {object|string} body used to determine if we'll be sending JSON
  @param {object} injectedHeaders optional
  @returns {Promise<object|CallProcessingError}
**/
export function buildHeaders(state, { headers, body }, injectedHeaders) {
  const stepName = '#buildHeaders';
  let headerMap = Object.assign({}, injectedHeaders || {}, headers);

  if (
    body &&
    typeof body === 'object' &&
    !headerMap.hasOwnProperty('Content-Type')
  ) headerMap['Content-Type'] = 'application/json';

  let ps = Object.keys(headerMap).map(header => {
    const headerVal = headerMap[header];

    if (typeof headerVal === 'function')
      try {
        return { key: header, value: headerVal(state) };
      } catch (error) {
        const msg = `Error thrown when evaluating header function ${header}`;
        return Promise.reject(new CallProcessingError(msg, stepName, error));
      }
    else if (isPromise(headerVal))
      return headerVal.then(v => ({ key: header, value: v }));
    // if you're not a promise but still an object we don't want you
    else if (typeof headerVal === 'object')
      return Promise.reject(
        new CallProcessingError(`Object given for header ${header}`, stepName)
      );
    // I guess it's just a plain value
    else return { key: header, value: headerVal };
  });

  return Promise.all(ps).then(p => p
    .filter(pair => pair.value !== false)
    .reduce((acc, pair) => { acc[pair.key] = pair.value; return acc; }, {})
  );
}

/**
  @private
  @name parseBody
  @desc detects if the request body is an object, if so will attempt to parse
    it as JSON, otherwise just returning it; if body is a function it will be
    called with the state passed in - supports promises
  @param {object} state
  @param {any} body
  @returns {Promise<string>} parsedBody
**/
export function parseBody(state, body) {
  const stepName = '#parseBody';
  if (!body) return Promise.resolve(null);
  else if (isPromise(body))
    return body.then(realBody => parseBody(state, realBody));
  else if (typeof body === 'object')
    try {
      return Promise.resolve(JSON.stringify(body));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error parsing body', stepName, error
      ));
    }
  else if (typeof body === 'function')
    try {
      return Promise.resolve(body(state));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error occurred while evaluating body function', stepName, error
      ));
    }
  else return Promise.resolve(body);
}

/**
  @private
  @name makeStatusActions
  @desc parses the status action configuration into actionable functions
  @param {object} ogActions original status action configuration, keys
    are codes & values are a mix of values, functions, promises, or objects
  @param {function} dispatch
  @returns {Promise<object>} statusActions where keys are codes & values
    are callbacks
**/
export function makeStatusActions(ogActions, dispatch) {
  const makeCallbackFromValue = val => {
    // functions are called w/ the response object
    if (typeof val === 'function')
      return Promise.resolve(res => val(dispatch, res));
    else if (isPromise(val))
      return val.then(newVal => makeCallbackFromValue(newVal));
    else if (typeof val === 'object')
      return Promise.resolve(() => dispatch(val));
    return Promise.resolve(() => dispatch({ type: val }));
  };

  let ps = Object.keys(ogActions)
  .map(code => makeCallbackFromValue(ogActions[code])
    .then(callback => ({ code, callback }))
  );

  return Promise.all(ps).then(entries => entries
    .reduce((acc, entry) => {
      acc[entry.code] = entry.callback;
      return acc;
    }, {})
  );
}

/**
  @private
  @name makeRemoteCallHooks
  @desc parses the given lifecycle hooks, providing redirections for the
    remote call API and giving the correct args for each hook
  @param {object} lifecycleHooks contains the orginal user lifecycle hooks
  @param {any} lifecycleHooks.request
  @param {any} lifecycleHooks.success
  @param {any} lifecycleHooks.failure
  @param {function} dispatch
  @returns {Promise<object>} remoteCallHooks
**/
export function makeRemoteCallHooks(lifecycleHooks, dispatch) {
  const stepName = '#makeRemoteCallHooks';

  // ensure that for each hook we can call a callback on fn values so
  // that we can provide the set of arguments for that hook
  const redirectHook = (hookVal, cb) => {
    if (!hookVal)
      // if hook is left out just make the call hook a noop
      return Promise.resolve(() => {});
    else if (typeof hookVal === 'function')
      return Promise.resolve(cb(hookVal));
    else if (isPromise(hookVal))
      return hookVal.then(v => redirectHook(v, cb));
    else if (typeof hookVal === 'object')
      return Promise.resolve(() => dispatch(hookVal));
    return Promise.resolve(() => dispatch({ type: hookVal }));
  };

  const reqCb = fn => () => fn(dispatch);
  const sucCb = fn => (data, res) => fn(data, dispatch, res);
  const errCb = fn => (status, data, res) => {
    const msg = data.error || data.err || res.statusText;
    fn(new APIError(status, msg, res), dispatch, data, res);
  };

  return Promise.all([
    redirectHook(lifecycleHooks.request, reqCb),
    redirectHook(lifecycleHooks.success, sucCb),
    redirectHook(lifecycleHooks.failure, errCb)
  ]).then(([ onBeforeCall, onCallSuccess, onCallFailure ]) =>
    ({ onBeforeCall, onCallSuccess, onCallFailure })
  ).catch(error => {
    const msg = 'Error thrown while processing lifecycle hooks';
    Promise.reject(new CallProcessingError(msg, stepName, error));
  });
}
<|endoftext|>


<commit_before>import {
  isArray,
  isMaybeThenable
} from './utils';

import {
  noop,
  resolve,
  handleMaybeThenable,
  reject,
  fulfill,
  subscribe,
  FULFILLED,
  REJECTED,
  PENDING,
  getThen
} from './-internal';

import Promise from './promise';
import originalThen from './then';
import originalResolve from './promise/resolve';

export function makeSettledResult(state, position, value) {
  if (state === FULFILLED) {
    return {
      state: 'fulfilled',
      value: value
    };
  } else {
     return {
      state: 'rejected',
      reason: value
    };
  }
}

function Enumerator(Constructor, input, abortOnReject, label) {
  var enumerator = this;

  enumerator._instanceConstructor = Constructor;
  enumerator.promise = new Constructor(noop, label);
  enumerator._abortOnReject = abortOnReject;

  if (enumerator._validateInput(input)) {
    enumerator._input     = input;
    enumerator.length     = input.length;
    enumerator._remaining = input.length;

    enumerator._init();

    if (enumerator.length === 0) {
      fulfill(enumerator.promise, enumerator._result);
    } else {
      enumerator.length = enumerator.length || 0;
      enumerator._enumerate();
      if (enumerator._remaining === 0) {
        fulfill(enumerator.promise, enumerator._result);
      }
    }
  } else {
    reject(enumerator.promise, enumerator._validationError());
  }
}

export default Enumerator;

Enumerator.prototype._validateInput = function(input) {
  return isArray(input);
};

Enumerator.prototype._validationError = function() {
  return new Error('Array Methods must be provided an Array');
};

Enumerator.prototype._init = function() {
  this._result = new Array(this.length);
};

Enumerator.prototype._enumerate = function() {
  var enumerator = this;
  var length     = enumerator.length;
  var promise    = enumerator.promise;
  var input      = enumerator._input;

  for (var i = 0; promise._state === PENDING && i < length; i++) {
    enumerator._eachEntry(input[i], i);
  }
};

Enumerator.prototype._eachEntry = function(entry, i) {
  var enumerator = this;
  var c = enumerator._instanceConstructor;
  var resolve = c.resolve;
  if (isMaybeThenable(entry)) {
    if (resolve === originalResolve) {
      var then = getThen(entry);

      if (then === originalThen &&
          entry._state !== PENDING) {
          entry._onError = null;
          enumerator._settledAt(entry._state, i, entry._result);
      } else if (typeof then !== 'function') {
        enumerator._result[i] = enumerator._makeResult(FULFILLED, i, entry);
      } else if (c === Promise) {
        var promise = new c(noop);
        handleMaybeThenable(promise, entry, then);
        enumerator._willSettleAt(promise, i);
      } else {
        // this path invokes `then` twice
        enumerator._willSettleAt(resolve(entry), i);
      }
    } else {
      enumerator._willSettleAt(resolve(entry), i);
    }
  } else {
    enumerator._remaining--;
    enumerator._result[i] = enumerator._makeResult(FULFILLED, i, entry);
  }
};

Enumerator.prototype._settledAt = function(state, i, value) {
  var enumerator = this;
  var promise = enumerator.promise;

  if (promise._state === PENDING) {
    enumerator._remaining--;

    if (enumerator._abortOnReject && state === REJECTED) {
      reject(promise, value);
    } else {
      enumerator._result[i] = enumerator._makeResult(state, i, value);
    }
  }

  if (enumerator._remaining === 0) {
    fulfill(promise, enumerator._result);
  }
};

Enumerator.prototype._makeResult = function(state, i, value) {
  return value;
};

Enumerator.prototype._willSettleAt = function(promise, i) {
  var enumerator = this;

  subscribe(promise, undefined, function(value) {
    enumerator._settledAt(FULFILLED, i, value);
  }, function(reason) {
    enumerator._settledAt(REJECTED, i, reason);
  });
};
<commit_msg>ensure we don’t access `then` twice, if resolve has been tampered but then has not, and constructor is different<commit_after>import {
  isArray,
  isMaybeThenable
} from './utils';

import {
  noop,
  resolve,
  handleMaybeThenable,
  reject,
  fulfill,
  subscribe,
  FULFILLED,
  REJECTED,
  PENDING,
  getThen
} from './-internal';

import Promise from './promise';
import originalThen from './then';
import originalResolve from './promise/resolve';

export function makeSettledResult(state, position, value) {
  if (state === FULFILLED) {
    return {
      state: 'fulfilled',
      value: value
    };
  } else {
     return {
      state: 'rejected',
      reason: value
    };
  }
}

function Enumerator(Constructor, input, abortOnReject, label) {
  var enumerator = this;

  enumerator._instanceConstructor = Constructor;
  enumerator.promise = new Constructor(noop, label);
  enumerator._abortOnReject = abortOnReject;

  if (enumerator._validateInput(input)) {
    enumerator._input     = input;
    enumerator.length     = input.length;
    enumerator._remaining = input.length;

    enumerator._init();

    if (enumerator.length === 0) {
      fulfill(enumerator.promise, enumerator._result);
    } else {
      enumerator.length = enumerator.length || 0;
      enumerator._enumerate();
      if (enumerator._remaining === 0) {
        fulfill(enumerator.promise, enumerator._result);
      }
    }
  } else {
    reject(enumerator.promise, enumerator._validationError());
  }
}

export default Enumerator;

Enumerator.prototype._validateInput = function(input) {
  return isArray(input);
};

Enumerator.prototype._validationError = function() {
  return new Error('Array Methods must be provided an Array');
};

Enumerator.prototype._init = function() {
  this._result = new Array(this.length);
};

Enumerator.prototype._enumerate = function() {
  var enumerator = this;
  var length     = enumerator.length;
  var promise    = enumerator.promise;
  var input      = enumerator._input;

  for (var i = 0; promise._state === PENDING && i < length; i++) {
    enumerator._eachEntry(input[i], i);
  }
};

Enumerator.prototype._eachEntry = function(entry, i) {
  var enumerator = this;
  var c = enumerator._instanceConstructor;
  var resolve = c.resolve;
  if (isMaybeThenable(entry)) {
    if (resolve === originalResolve) {
      var then = getThen(entry);

      if (then === originalThen &&
          entry._state !== PENDING) {
          entry._onError = null;
          enumerator._settledAt(entry._state, i, entry._result);
      } else if (typeof then !== 'function') {
        enumerator._result[i] = enumerator._makeResult(FULFILLED, i, entry);
      } else if (c === Promise) {
        var promise = new c(noop);
        handleMaybeThenable(promise, entry, then);
        enumerator._willSettleAt(promise, i);
      } else {
        enumerator._willSettleAt(new c(function(resolve) { resolve(entry); }), i);
      }
    } else {
      enumerator._willSettleAt(resolve(entry), i);
    }
  } else {
    enumerator._remaining--;
    enumerator._result[i] = enumerator._makeResult(FULFILLED, i, entry);
  }
};

Enumerator.prototype._settledAt = function(state, i, value) {
  var enumerator = this;
  var promise = enumerator.promise;

  if (promise._state === PENDING) {
    enumerator._remaining--;

    if (enumerator._abortOnReject && state === REJECTED) {
      reject(promise, value);
    } else {
      enumerator._result[i] = enumerator._makeResult(state, i, value);
    }
  }

  if (enumerator._remaining === 0) {
    fulfill(promise, enumerator._result);
  }
};

Enumerator.prototype._makeResult = function(state, i, value) {
  return value;
};

Enumerator.prototype._willSettleAt = function(promise, i) {
  var enumerator = this;

  subscribe(promise, undefined, function(value) {
    enumerator._settledAt(FULFILLED, i, value);
  }, function(reason) {
    enumerator._settledAt(REJECTED, i, reason);
  });
};
<|endoftext|>


<commit_before>var protocols = ['G', 'host', 'req', 'rule', 'res', 'weinre', 'proxy',
  'https2http-proxy', 'http2https-proxy', 'internal-proxy', 'pac', 'filter',
  'ignore', 'enable', 'disable', 'delete', 'log', 'plugin', 'exports',
  'exportsUrl', 'hostname', 'referer', 'auth', 'ua', 'etag', 'urlParams',
  'dispatch', 'params', 'resMerge', 'statusCode', 'replaceStatus',
  'redirect', 'method', 'cache', 'attachment', 'forwardedFor', 'responseFor',
  'location', 'accept', 'rulesFile', 'resScript', 'reqDelay', 'resDelay',
  'reqSpeed', 'resSpeed', 'reqType', 'resType', 'reqCharset',
  'resCharset', 'reqCookies', 'resCookies', 'reqCors', 'resCors',
  'reqHeaders', 'resHeaders', 'reqPrepend', 'resPrepend', 'reqBody',
  'resBody', 'reqAppend', 'resAppend', 'urlReplace', 'reqReplace',
  'resReplace', 'reqWrite', 'resWrite', 'reqWriteRaw', 'resWriteRaw',
  'cssAppend', 'htmlAppend', 'jsAppend', 'cssBody', 'htmlBody', 'jsBody',
  'cssPrepend', 'htmlPrepend', 'jsPrepend'
];

var RULE_RE = /^(?:|x|xs)(?:file|rawfile|dust|tpl|jsonp):$/;
var resProtocols = ['res', 'filter', 'disable', 'ignore', 'replaceStatus',
  'cache', 'attachment', 'delete', 'resMerge', 'location', 'resDelay',
  'resSpeed', 'resType', 'resType', 'resCharset', 'resCookies', 'resCors',
  'resHeaders', 'resPrepend', 'resBody', 'resAppend', 'resReplace',
  'resWrite', 'resWriteRaw', 'cssAppend', 'htmlAppend', 'jsAppend', 'cssBody',
  'htmlBody', 'jsBody', 'cssPrepend', 'htmlPrepend', 'jsPrepend',
  'responseFor'
];
var binProtocols = ['reqPrepend', 'resPrepend', 'reqBody', 'resBody',
  'reqAppend', 'resAppend', 'cssAppend', 'htmlAppend', 'jsAppend', 'cssBody',
  'htmlBody', 'jsBody', 'cssPrepend', 'htmlPrepend', 'jsPrepend'
];
var aliasProtocols = {
  ruleFile: 'rulesFile',
  ruleScript: 'rulesFile',
  rulesScript: 'rulesFile',
  reqScript: 'rulesFile',
  download: 'attachment',
  exportUrl: 'exportsUrl',
  'http-proxy': 'proxy',
  'xhttp-proxy': 'xproxy',
  export: 'exports',
  status: 'statusCode',
  reqHost: 'hostname',
  hosts: 'host',
  xhost: 'host',
  html: 'htmlAppend',
  js: 'jsAppend',
  reqMerge: 'params',
  css: 'cssAppend'
};

exports.multiMatchs = [
  'ignore', 'enable', 'filter', 'disable', 'plugin',
  'urlParams', 'params', 'reqHeaders', 'resHeaders', 'reqCors', 'resCors',
  'reqCookies', 'resCookies', 'reqReplace', 'urlReplace', 'resReplace',
  'resMerge', 'reqBody', 'reqPrepend', 'resPrepend', 'reqAppend', 'resAppend',
  'resBody', 'htmlAppend', 'jsAppend', 'cssAppend', 'htmlBody', 'jsBody',
  'cssBody', 'htmlPrepend', 'jsPrepend', 'cssPrepend'
];
exports.protocols = protocols;
exports.resProtocols = resProtocols;
exports.aliasProtocols = aliasProtocols;

function getRules() {
  return resetRules({});
}

exports.getRules = getRules;

function isBinProtocol(protocol) {

  return binProtocols.indexOf(protocol) != -1;
}

exports.isBinProtocol = isBinProtocol;

function resetRules(rules) {
  protocols.forEach(function (protocol) {
    rules[protocol] = [];
  });
  rules._localRule = [];
  return rules;
}

exports.resetRules = resetRules;

function isResRule(protocol) {
  return resProtocols.indexOf(protocol) != -1;
}

exports.isResRule = isResRule;

function isWebProtocol(protocol) {
  return protocol == 'http:' || protocol == 'https:';
}

exports.isWebProtocol = isWebProtocol;

function isWebsocketProtocol(protocol) {
  return protocol == 'ws:' || protocol == 'wss:';
}

exports.isWebsocketProtocol = isWebsocketProtocol;

function isFileProxy(protocol) {
  return RULE_RE.test(protocol);
}

exports.isFileProxy = isFileProxy;

function contains(name) {
  if (protocols.indexOf(name) != -1 || name == 'socks' || aliasProtocols[name]) {
    return true;
  }
  name += ':';
  return isWebsocketProtocol(name) || isWebProtocol(name) || isFileProxy(name) ||
    name == 'tunnel:';
}

exports.contains = contains;
<commit_msg>feat: support matching multiple rules with the same name<commit_after>var protocols = ['G', 'host', 'req', 'rule', 'res', 'weinre', 'proxy',
  'https2http-proxy', 'http2https-proxy', 'internal-proxy', 'pac', 'filter',
  'ignore', 'enable', 'disable', 'delete', 'log', 'plugin', 'exports',
  'exportsUrl', 'hostname', 'referer', 'auth', 'ua', 'etag', 'urlParams',
  'dispatch', 'params', 'resMerge', 'statusCode', 'replaceStatus',
  'redirect', 'method', 'cache', 'attachment', 'forwardedFor', 'responseFor',
  'location', 'accept', 'rulesFile', 'resScript', 'reqDelay', 'resDelay',
  'reqSpeed', 'resSpeed', 'reqType', 'resType', 'reqCharset',
  'resCharset', 'reqCookies', 'resCookies', 'reqCors', 'resCors',
  'reqHeaders', 'resHeaders', 'reqPrepend', 'resPrepend', 'reqBody',
  'resBody', 'reqAppend', 'resAppend', 'urlReplace', 'reqReplace',
  'resReplace', 'reqWrite', 'resWrite', 'reqWriteRaw', 'resWriteRaw',
  'cssAppend', 'htmlAppend', 'jsAppend', 'cssBody', 'htmlBody', 'jsBody',
  'cssPrepend', 'htmlPrepend', 'jsPrepend'
];

var RULE_RE = /^(?:|x|xs)(?:file|rawfile|dust|tpl|jsonp):$/;
var resProtocols = ['res', 'filter', 'disable', 'ignore', 'replaceStatus',
  'cache', 'attachment', 'delete', 'resMerge', 'location', 'resDelay',
  'resSpeed', 'resType', 'resType', 'resCharset', 'resCookies', 'resCors',
  'resHeaders', 'resPrepend', 'resBody', 'resAppend', 'resReplace',
  'resWrite', 'resWriteRaw', 'cssAppend', 'htmlAppend', 'jsAppend', 'cssBody',
  'htmlBody', 'jsBody', 'cssPrepend', 'htmlPrepend', 'jsPrepend',
  'responseFor'
];
var binProtocols = ['reqPrepend', 'resPrepend', 'reqBody', 'resBody',
  'reqAppend', 'resAppend', 'cssAppend', 'htmlAppend', 'jsAppend', 'cssBody',
  'htmlBody', 'jsBody', 'cssPrepend', 'htmlPrepend', 'jsPrepend'
];
var aliasProtocols = {
  ruleFile: 'rulesFile',
  ruleScript: 'rulesFile',
  rulesScript: 'rulesFile',
  reqScript: 'rulesFile',
  download: 'attachment',
  exportUrl: 'exportsUrl',
  'http-proxy': 'proxy',
  'xhttp-proxy': 'xproxy',
  export: 'exports',
  status: 'statusCode',
  reqHost: 'hostname',
  hosts: 'host',
  xhost: 'host',
  html: 'htmlAppend',
  js: 'jsAppend',
  reqMerge: 'params',
  css: 'cssAppend'
};

exports.multiMatchs = [
  'ignore', 'enable', 'filter', 'disable', 'plugin', 'delete',
  'urlParams', 'params', 'reqHeaders', 'resHeaders', 'reqCors', 'resCors',
  'reqCookies', 'resCookies', 'reqReplace', 'urlReplace', 'resReplace',
  'resMerge', 'reqBody', 'reqPrepend', 'resPrepend', 'reqAppend', 'resAppend',
  'resBody', 'htmlAppend', 'jsAppend', 'cssAppend', 'htmlBody', 'jsBody',
  'cssBody', 'htmlPrepend', 'jsPrepend', 'cssPrepend'
];
exports.protocols = protocols;
exports.resProtocols = resProtocols;
exports.aliasProtocols = aliasProtocols;

function getRules() {
  return resetRules({});
}

exports.getRules = getRules;

function isBinProtocol(protocol) {

  return binProtocols.indexOf(protocol) != -1;
}

exports.isBinProtocol = isBinProtocol;

function resetRules(rules) {
  protocols.forEach(function (protocol) {
    rules[protocol] = [];
  });
  rules._localRule = [];
  return rules;
}

exports.resetRules = resetRules;

function isResRule(protocol) {
  return resProtocols.indexOf(protocol) != -1;
}

exports.isResRule = isResRule;

function isWebProtocol(protocol) {
  return protocol == 'http:' || protocol == 'https:';
}

exports.isWebProtocol = isWebProtocol;

function isWebsocketProtocol(protocol) {
  return protocol == 'ws:' || protocol == 'wss:';
}

exports.isWebsocketProtocol = isWebsocketProtocol;

function isFileProxy(protocol) {
  return RULE_RE.test(protocol);
}

exports.isFileProxy = isFileProxy;

function contains(name) {
  if (protocols.indexOf(name) != -1 || name == 'socks' || aliasProtocols[name]) {
    return true;
  }
  name += ':';
  return isWebsocketProtocol(name) || isWebProtocol(name) || isFileProxy(name) ||
    name == 'tunnel:';
}

exports.contains = contains;
<|endoftext|>


<commit_before>/* global Log, Config */

'use strict';

const EventEmitter = require('events').EventEmitter;
const Crypto = require('crypto');
const Path = require('path');
const Aws = require('aws-sdk');

const DEFAULT_INTERVAL = 60000;
const DEFAULT_PARALLEL = 16;
const METADATA_LIST = /^(\d+)=(.+)$/;
const METADATA_TIMEOUT = 500;

/**
 * Iterate over a list of tasks in parallel, passing one to each call of the
 * `work` function. When all tasks are complete, or an error is encountered, call the
 * `done` function, with an error if one occurred.
 *
 * This implementation iterates over a work-list non-destructively, without cloning it.
 * meaning that more tasks can be added safely while the work-loop is running.
 *
 * @param  {Array}    list    Set of tasks to work on
 * @param  {Function} work    The operation to perform on each element of `list`
 * @param  {Function} done    Callback on error or completion
 * @param  {Object}   s { parallel: Number limit parallel tasks. Default 16 }
 */
function each(list, work, done, s) {
  const state = s || {
    parallel: DEFAULT_PARALLEL,
    error: false
  };

  // Default values
  if (!Number(state.running)) {
    state.running = 0;
  }
  if (!Number(state.cursor)) {
    state.cursor = 0;
  }

  // No more items to process
  if (state.cursor >= list.length) {
    // Call done if this was the last branch
    if (state.running === 0 && !state.error) {
      done();
    }

    return;
  }

  // Already enough tasks in flight
  if (state.running >= state.parallel) {
    return;
  }

  // Get an item off of the list, move up the cursor, and get a semaphore.
  const item = list[state.cursor];

  // Obtain a semaphore
  state.running += 1;
  state.cursor += 1;

  // Branch parallel requests
  while (state.running < state.parallel && state.cursor < list.length) {
    each(list, work, done, state);
  }

  // Process this branch's task
  work(item, (err) => {
    // Release the semaphore
    state.running -= 1;

    // An error has occurred. Just bail.
    if (state.error) {
      return;
    }

    if (err) {
      state.error = true;
      return done(err); // eslint-disable-line consistent-return
    }

    // Iterate
    each(list, work, done, state);
  });
}

/**
 * Metadata Parser
 *
 * @class MetadataParser
 *
 */
class MetadataParser {
  constructor(options) {
    this.properties = {};
    this.namespace = options.namespace;
  }

  update(data) {
    const properties = {};

    // Get region and account ID from the ID document
    try {
      const identity = JSON.parse(data['dynamic/instance-identity/document']);

      properties.account = identity.accountId;
      properties.region = identity.region;
    } catch (e) {
      /* Don't do anything */
    }

    // Expose instance identification parameters
    properties.identity = {
      document: data['dynamic/instance-identity/document'],
      dsa2048: data['dynamic/instance-identity/dsa2048'],
      pkcs7: data['dynamic/instance-identity/pkcs7'],
      signature: data['dynamic/instance-identity/signature']
    };

    // Get instance profile credentials
    const IAM_METADATA_PATH = 'meta-data/iam/security-credentials/';
    const credentialsPaths = Object.keys(data).filter((path) =>
    Path.relative(IAM_METADATA_PATH, path).slice(0, 2) !== '..');

    // Use the first set of credentials
    if (credentialsPaths.length > 0) {
      try {
        const credentials = JSON.parse(data[credentialsPaths[0]]);

        properties.credentials = {
          lastUpdated: credentials.LastUpdated,
          type: credentials.Type,
          accessKeyId: credentials.AccessKeyId,
          secretAccessKey: credentials.SecretAccessKey,
          expires: credentials.Expiration
        };
      } catch (e) {
        /* Don't do anything */
      }
    }

    // Common meta-data properties
    ['local-ipv4', 'local-hostname', 'instance-type', 'instance-id', 'hostname',
      'ami-id', 'public-hostname', 'public-ipv4', 'public-keys', 'reservation-id',
      'security-groups'].forEach((name) => properties[name] = data[Path.join('meta-data', name)]);

    properties['availability-zone'] = data['meta-data/placement/availability-zone'];

    // Grok the network interface parameters
    const mac = data['meta-data/mac'];

    if (mac) {
      const interfacePath = Path.join('meta-data/network/interfaces/macs', mac);

      properties['vpc-id'] = data[Path.join(interfacePath, 'vpc-id')];
      properties['subnet-id'] = data[Path.join(interfacePath, 'subnet-id')];

      const interfaceProperties = properties.interface = {};

      ['vpc-ipv4-cidr-block', 'subnet-ipv4-cidr-block', 'public-ipv4s', 'mac',
        'local-ipv4s', 'interface-id'
      ].forEach((name) => interfaceProperties[name] = data[Path.join(interfacePath, name)]);
    }

    this.properties = {};
    this.properties[this.namespace] = properties;
  }
}

/**
 * Metadata Source
 *
 * Expose EC2 metadata to the property-set
 *
 * @class Source.Metadata
 * @extends EventEmitter
 *
 * @param {Parser} parser
 * @param {Object} options
 *      - {Number} interval Polling interval. Default 30s.
 */
class Metadata extends EventEmitter {
  constructor(opts) {
    super();
    const options = opts || {};

    if (!options.hasOwnProperty('name')) {
      options.name = 'ec2-metadata';
    }
    if (!options.hasOwnProperty('namespace')) {
      options.namespace = 'instance';
    }

    this.interval = DEFAULT_INTERVAL;
    this.type = 'ec2-metadata';
    this._parser = new MetadataParser(options);
    this.name = options.name || 'source';
    this._ok = false;
    this._updated = null;
    this.properties = {};

    /**
     * Initialize the metadata-service client
     */
    this.service = new Aws.MetadataService({
      httpOptions: {
        timeout: METADATA_TIMEOUT
      },
      host: Config.get('metadata:host')
    });

    this.configure(options);
    this.signature = null;
  }

  /**
   * Check the status of the polling interval loop
   *
   * @return {Boolean} True if the pooling loop is running
   */
  get _running() {
    return !!this._timer;
  }

  /**
   * Metadata version
   *
   * Used to prepend paths for calls to the EC2 Metadata Service,
   * e.g. /<version>/meta-data/ami-id
   *
   * @returns {string}
   * @private
   */
  get _version() {
    return this.constructor.version;
  }

  /**
   * @param {Object} params
   * @returns {Boolean}
   */
  configure(params) {
    return Metadata.setIfChanged(this, 'interval', params.interval || DEFAULT_INTERVAL) || false;
  }

  /**
   * Start the polling interval loop
   *
   * @return {Metadata} Reference to instance
   */
  initialize() {
    if (this._timer) {
      return this;
    }

    Log.info(`Initializing ${this.type} source ${this.name}`, {
      source: this.name,
      type: this.type
    });

    // Initialize state to 'RUNNING'
    this._timer = true;
    const _this = this;

    this.emit('startup');

    (function poll() {
      const timer = Date.now();

      Log.debug(`Polling source ${_this.name} for updates`, {
        source: _this.name,
        type: _this.type
      });

      _this._fetch((err, data) => {
        // If `shutdown()` was called _during_ a fetch operation, `clearTimeout()`
        // will have no effect. Instead, we overload `_interval` as a state to detect
        // if a shutdown has been initiated. False-y -> don't set another timeout.
        if (_this._running) {
          _this._timer = setTimeout(poll, _this.interval);
        }
        if (err) {
          return _this._error(err);
        }

        Log.debug(`Polled source ${_this.name} in ${(Date.now() - timer)}ms`, {
          source: _this.name,
          type: _this.type
        });

        if (data) {
          return _this._update(data);
        }
        _this.emit('no-update');

        Log.debug(`Source ${_this.name} is up to date`, {
          source: _this.name,
          type: _this.type
        });
      });

      // Start the polling loop
    }());

    return this;
  }

  /**
   * Stop the polling interval loop
   *
   * @return {Metadata} Receiver
   */
  shutdown() {
    if (!this._running) {
      return this;
    }

    this._ok = false;
    this.signature = null;

    Log.info(`Shutting down ${this.type} source ${this.name}`, {
      source: this.name,
      type: this.type
    });

    clearTimeout(this._timer);
    delete this._timer;

    this.emit('shutdown');
    return this;
  }

  /**
   * Return an object describing the source-instance's current status
   *
   * @return {Object}
   */
  status() {
    return {
      ok: this._ok,
      updated: this._updated,
      interval: this.interval,
      running: this._running
    };
  }

  /**
   * Clear the underlying properties object
   */
  clear() {
    this.properties = {};
  }

  /**
   * Fetch all values from the EC2 metadata tree
   *
   * @param  {Function} callback(err, paths) Return an error or a hash of paths and their values
   * @private
   */
  _traverse(callback) {
    const values = {};
    const paths = ['/meta-data/', '/dynamic/'];

    each(paths, (path, next) => {
      this.service.request(Path.join('/', this._version, path), (err, data) => {
        if (err) {
          return next(err);
        }

        // This is a list! Split new-line delimited strings into an array and add to tail of paths
        if (path.slice(-1) === '/') {
          const items = data.trim().split('\n');

          // Is this a list?
          if (items.reduce((memo, item) => memo && METADATA_LIST.test(item), true)) {
            const list = values[path.slice(1, -1)] = [];

            items.forEach((item) => {
              const match = item.match(METADATA_LIST);

              if (match) {
                list[match[1]] = match[2];
              }
            });

            return next();
          }

          items.forEach((node) => paths.push(Path.join(path, node)));
          return next();
        }

        // Remove leading `/`
        values[path.slice(1)] = data;
        next();
      });
    }, (err) => callback(err, values), null);
  }

  /**
   * Called by implementations to update source data
   *
   * @param {Buffer}  data  Updated data from an implementation-specific source
   * @return {Metadata} Reference to instance
   * @private
   */
  _update(data) {
    this._parser.update(data);
    this.properties = this._parser.properties;
    this._updated = new Date();
    this._ok = true;

    Log.info(`Updated source ${this.name}`, {
      source: this.name,
      type: this.type
    });
    this.emit('update', this);

    return this;
  }

  /**
   * Handle errors from underlying source facilities
   *
   * @emits {Error} error If any listeners have been registered
   *
   * @param  {Error} err An instance of Error
   * @return {Metadata} Reference to instance
   * @private
   */
  _error(err) {
    this._ok = false;
    Log.error(err, {
      source: this.name,
      type: this.type
    });

    // Only emit an error event if there are listeners.
    if (this.listeners('error').length > 0) {
      this.emit('error', err);
    }

    return this;
  }

  /**
   *
   * @param {Function} callback
   * @private
   */
  _fetch(callback) {
    this._traverse((err, data) => {
      if (err) {
        return callback(err);
      }

      // Detect change by hashing the fetched data
      const hash = Crypto.createHash('sha1');

      Object.keys(data)
        .sort()
        .forEach((key) => {
          hash.update(`${key}:${data[key]}`);
        });

      const signature = hash.digest('base64');

      if (this.signature === signature) {
        return callback(null, false);
      }

      this.signature = signature;
      callback(null, data);
    });
  }

  /**
   * Helper method to detect parameter changes
   *
   * @param {Object}  scope The target object on which to set a parameter
   * @param {String}  key   The property of `scope` to set
   * @param {*}   value The value to compare and set
   * @return {Boolean} True if the parameter has been updated
   * @static
   */
  static setIfChanged(scope, key, value) {
    if (scope[key] === value) {
      return false;
    }

    scope[key] = value; // eslint-disable-line no-param-reassign
    return true;
  }
}

Metadata.version = 'latest';

/* Export */
module.exports = Metadata;
<commit_msg>Refactored metadata source slightly to allow passing a specific host in the constructor via the options object.<commit_after>/* global Log, Config */

'use strict';

const EventEmitter = require('events').EventEmitter;
const Crypto = require('crypto');
const Path = require('path');
const Aws = require('aws-sdk');

const DEFAULT_INTERVAL = 60000;
const DEFAULT_PARALLEL = 16;
const METADATA_LIST = /^(\d+)=(.+)$/;
const METADATA_TIMEOUT = 500;

/**
 * Iterate over a list of tasks in parallel, passing one to each call of the
 * `work` function. When all tasks are complete, or an error is encountered, call the
 * `done` function, with an error if one occurred.
 *
 * This implementation iterates over a work-list non-destructively, without cloning it.
 * meaning that more tasks can be added safely while the work-loop is running.
 *
 * @param  {Array}    list    Set of tasks to work on
 * @param  {Function} work    The operation to perform on each element of `list`
 * @param  {Function} done    Callback on error or completion
 * @param  {Object}   s { parallel: Number limit parallel tasks. Default 16 }
 */
function each(list, work, done, s) {
  const state = s || {
    parallel: DEFAULT_PARALLEL,
    error: false
  };

  // Default values
  if (!Number(state.running)) {
    state.running = 0;
  }
  if (!Number(state.cursor)) {
    state.cursor = 0;
  }

  // No more items to process
  if (state.cursor >= list.length) {
    // Call done if this was the last branch
    if (state.running === 0 && !state.error) {
      done();
    }

    return;
  }

  // Already enough tasks in flight
  if (state.running >= state.parallel) {
    return;
  }

  // Get an item off of the list, move up the cursor, and get a semaphore.
  const item = list[state.cursor];

  // Obtain a semaphore
  state.running += 1;
  state.cursor += 1;

  // Branch parallel requests
  while (state.running < state.parallel && state.cursor < list.length) {
    each(list, work, done, state);
  }

  // Process this branch's task
  work(item, (err) => {
    // Release the semaphore
    state.running -= 1;

    // An error has occurred. Just bail.
    if (state.error) {
      return;
    }

    if (err) {
      state.error = true;
      return done(err); // eslint-disable-line consistent-return
    }

    // Iterate
    each(list, work, done, state);
  });
}

/**
 * Metadata Parser
 *
 * @class MetadataParser
 *
 */
class MetadataParser {
  constructor(options) {
    this.properties = {};
    this.namespace = options.namespace;
  }

  update(data) {
    const properties = {};

    // Get region and account ID from the ID document
    try {
      const identity = JSON.parse(data['dynamic/instance-identity/document']);

      properties.account = identity.accountId;
      properties.region = identity.region;
    } catch (e) {
      /* Don't do anything */
    }

    // Expose instance identification parameters
    properties.identity = {
      document: data['dynamic/instance-identity/document'],
      dsa2048: data['dynamic/instance-identity/dsa2048'],
      pkcs7: data['dynamic/instance-identity/pkcs7'],
      signature: data['dynamic/instance-identity/signature']
    };

    // Get instance profile credentials
    const IAM_METADATA_PATH = 'meta-data/iam/security-credentials/';
    const credentialsPaths = Object.keys(data).filter((path) =>
    Path.relative(IAM_METADATA_PATH, path).slice(0, 2) !== '..');

    // Use the first set of credentials
    if (credentialsPaths.length > 0) {
      try {
        const credentials = JSON.parse(data[credentialsPaths[0]]);

        properties.credentials = {
          lastUpdated: credentials.LastUpdated,
          type: credentials.Type,
          accessKeyId: credentials.AccessKeyId,
          secretAccessKey: credentials.SecretAccessKey,
          expires: credentials.Expiration
        };
      } catch (e) {
        /* Don't do anything */
      }
    }

    // Common meta-data properties
    ['local-ipv4', 'local-hostname', 'instance-type', 'instance-id', 'hostname',
      'ami-id', 'public-hostname', 'public-ipv4', 'public-keys', 'reservation-id',
      'security-groups'].forEach((name) => properties[name] = data[Path.join('meta-data', name)]);

    properties['availability-zone'] = data['meta-data/placement/availability-zone'];

    // Grok the network interface parameters
    const mac = data['meta-data/mac'];

    if (mac) {
      const interfacePath = Path.join('meta-data/network/interfaces/macs', mac);

      properties['vpc-id'] = data[Path.join(interfacePath, 'vpc-id')];
      properties['subnet-id'] = data[Path.join(interfacePath, 'subnet-id')];

      const interfaceProperties = properties.interface = {};

      ['vpc-ipv4-cidr-block', 'subnet-ipv4-cidr-block', 'public-ipv4s', 'mac',
        'local-ipv4s', 'interface-id'
      ].forEach((name) => interfaceProperties[name] = data[Path.join(interfacePath, name)]);
    }

    this.properties = {};
    this.properties[this.namespace] = properties;
  }
}

/**
 * Metadata Source
 *
 * Expose EC2 metadata to the property-set
 *
 * @class Source.Metadata
 * @extends EventEmitter
 *
 * @param {Parser} parser
 * @param {Object} options
 *      - {Number} interval Polling interval. Default 30s.
 */
class Metadata extends EventEmitter {
  constructor(opts) {
    super();
    const options = opts || {};

    if (!options.hasOwnProperty('name')) {
      options.name = 'ec2-metadata';
    }
    if (!options.hasOwnProperty('namespace')) {
      options.namespace = 'instance';
    }

    this.interval = DEFAULT_INTERVAL;
    this.type = 'ec2-metadata';
    this._parser = new MetadataParser(options);
    this.name = options.name || 'source';
    this._ok = false;
    this._updated = null;
    this.properties = {};

    /**
     * Initialize the metadata-service client
     */
    const host = options.host || Config.get('metadata:host');

    this.service = new Aws.MetadataService({
      httpOptions: {
        timeout: METADATA_TIMEOUT
      },
      host
    });

    this.configure(options);
    this.signature = null;
  }

  /**
   * Check the status of the polling interval loop
   *
   * @return {Boolean} True if the pooling loop is running
   */
  get _running() {
    return !!this._timer;
  }

  /**
   * Metadata version
   *
   * Used to prepend paths for calls to the EC2 Metadata Service,
   * e.g. /<version>/meta-data/ami-id
   *
   * @returns {string}
   * @private
   */
  get _version() {
    return this.constructor.version;
  }

  /**
   * @param {Object} params
   * @returns {Boolean}
   */
  configure(params) {
    return Metadata.setIfChanged(this, 'interval', params.interval || DEFAULT_INTERVAL) || false;
  }

  /**
   * Start the polling interval loop
   *
   * @return {Metadata} Reference to instance
   */
  initialize() {
    if (this._timer) {
      return this;
    }

    Log.info(`Initializing ${this.type} source ${this.name}`, {
      source: this.name,
      type: this.type
    });

    // Initialize state to 'RUNNING'
    this._timer = true;
    const _this = this;

    this.emit('startup');

    (function poll() {
      const timer = Date.now();

      Log.debug(`Polling source ${_this.name} for updates`, {
        source: _this.name,
        type: _this.type
      });

      _this._fetch((err, data) => {
        // If `shutdown()` was called _during_ a fetch operation, `clearTimeout()`
        // will have no effect. Instead, we overload `_interval` as a state to detect
        // if a shutdown has been initiated. False-y -> don't set another timeout.
        if (_this._running) {
          _this._timer = setTimeout(poll, _this.interval);
        }
        if (err) {
          return _this._error(err);
        }

        Log.debug(`Polled source ${_this.name} in ${(Date.now() - timer)}ms`, {
          source: _this.name,
          type: _this.type
        });

        if (data) {
          return _this._update(data);
        }
        _this.emit('no-update');

        Log.debug(`Source ${_this.name} is up to date`, {
          source: _this.name,
          type: _this.type
        });
      });

      // Start the polling loop
    }());

    return this;
  }

  /**
   * Stop the polling interval loop
   *
   * @return {Metadata} Receiver
   */
  shutdown() {
    if (!this._running) {
      return this;
    }

    this._ok = false;
    this.signature = null;

    Log.info(`Shutting down ${this.type} source ${this.name}`, {
      source: this.name,
      type: this.type
    });

    clearTimeout(this._timer);
    delete this._timer;

    this.emit('shutdown');
    return this;
  }

  /**
   * Return an object describing the source-instance's current status
   *
   * @return {Object}
   */
  status() {
    return {
      ok: this._ok,
      updated: this._updated,
      interval: this.interval,
      running: this._running
    };
  }

  /**
   * Clear the underlying properties object
   */
  clear() {
    this.properties = {};
  }

  /**
   * Fetch all values from the EC2 metadata tree
   *
   * @param  {Function} callback(err, paths) Return an error or a hash of paths and their values
   * @private
   */
  _traverse(callback) {
    const values = {};
    const paths = ['/meta-data/', '/dynamic/'];

    each(paths, (path, next) => {
      this.service.request(Path.join('/', this._version, path), (err, data) => {
        if (err) {
          return next(err);
        }

        // This is a list! Split new-line delimited strings into an array and add to tail of paths
        if (path.slice(-1) === '/') {
          const items = data.trim().split('\n');

          // Is this a list?
          if (items.reduce((memo, item) => memo && METADATA_LIST.test(item), true)) {
            const list = values[path.slice(1, -1)] = [];

            items.forEach((item) => {
              const match = item.match(METADATA_LIST);

              if (match) {
                list[match[1]] = match[2];
              }
            });

            return next();
          }

          items.forEach((node) => paths.push(Path.join(path, node)));
          return next();
        }

        // Remove leading `/`
        values[path.slice(1)] = data;
        next();
      });
    }, (err) => callback(err, values), null);
  }

  /**
   * Called by implementations to update source data
   *
   * @param {Buffer}  data  Updated data from an implementation-specific source
   * @return {Metadata} Reference to instance
   * @private
   */
  _update(data) {
    this._parser.update(data);
    this.properties = this._parser.properties;
    this._updated = new Date();
    this._ok = true;

    Log.info(`Updated source ${this.name}`, {
      source: this.name,
      type: this.type
    });
    this.emit('update', this);

    return this;
  }

  /**
   * Handle errors from underlying source facilities
   *
   * @emits {Error} error If any listeners have been registered
   *
   * @param  {Error} err An instance of Error
   * @return {Metadata} Reference to instance
   * @private
   */
  _error(err) {
    this._ok = false;
    Log.error(err, {
      source: this.name,
      type: this.type
    });

    // Only emit an error event if there are listeners.
    if (this.listeners('error').length > 0) {
      this.emit('error', err);
    }

    return this;
  }

  /**
   *
   * @param {Function} callback
   * @private
   */
  _fetch(callback) {
    this._traverse((err, data) => {
      if (err) {
        return callback(err);
      }

      // Detect change by hashing the fetched data
      const hash = Crypto.createHash('sha1');

      Object.keys(data)
        .sort()
        .forEach((key) => {
          hash.update(`${key}:${data[key]}`);
        });

      const signature = hash.digest('base64');

      if (this.signature === signature) {
        return callback(null, false);
      }

      this.signature = signature;
      callback(null, data);
    });
  }

  /**
   * Helper method to detect parameter changes
   *
   * @param {Object}  scope The target object on which to set a parameter
   * @param {String}  key   The property of `scope` to set
   * @param {*}   value The value to compare and set
   * @return {Boolean} True if the parameter has been updated
   * @static
   */
  static setIfChanged(scope, key, value) {
    if (scope[key] === value) {
      return false;
    }

    scope[key] = value; // eslint-disable-line no-param-reassign
    return true;
  }
}

Metadata.version = 'latest';

/* Export */
module.exports = Metadata;
<|endoftext|>


<commit_before>var refresh_ticker = function(){};

$(function(){
    var $ttemplate = $("#tickers-template");
    if ($ttemplate.length) {
        var template = Handlebars.compile($ttemplate.html());

        refresh_ticker = function(){
            API.ticker().success(function(res){
                var tickers = res.result;
                for (var i = 0; i < tickers.length; i++) {
                    if (Number(tickers[i].last) > Number(tickers[i].first)) {
                        tickers[i].color = "green";
                        tickers[i].icon = "chevron-up";
                    } else if (Number(tickers[i].last) < Number(tickers[i].first)) {
                        tickers[i].color = "red";
                        tickers[i].icon = "chevron-down";
                    } else {
                        tickers[i].color = "gray";
                        tickers[i].icon = "minus";
                    }
                    tickers[i].last = zerosTrim(tickers[i].last);
                }
                $('#ticker').html(template(tickers));
            }).error(function(){
                $.pnotify({
                    title: 'Failed to load ticker.',
                    text: 'Reason: ' + (JSON.parse(res.responseText)).error,
                    styling: 'bootstrap',
                    type: 'error',
                    text_escape: true
                });
            });
        };
        refresh_ticker();
    }
    showEmails();
});

function showEmails() {
    $('hidden-email').each(function(el){
        var $el = $(el);
        $el.text($el.attr('name') + '@' + $el.attr('domain'));
        $el.attr('href', 'mailto:' + $el.text());
    });
}

function keepNumLength(m){
    var i = m.length - 1;
    while (m[i] == '0') {
        i--;
    }
    if (m[i] == '.') {
        i++;
    }
    return i;
}

function zerosToSpaces(m){
    m = Number(m).toFixed(8);
    var i = keepNumLength(m);
    return m.substring(0, i + 1) + Array(m.length - i).join('\xA0');
}

function zerosTrim(m){
    m = Number(m).toFixed(8);
    var i = keepNumLength(m);
    return m.substring(0, i + 1);
}

// Google analytics

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-54524343-1', 'auto');
ga('send', 'pageview');

// Include the UserVoice JavaScript SDK (only needed once on a page)
UserVoice=window.UserVoice||[];(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/xDR0gQn4m8VcCpTur0j4Q.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})();

//
// UserVoice Javascript SDK developer documentation:
// https://www.uservoice.com/o/javascript-sdk
//

// Set colors
UserVoice.push(['set', {
    accent_color: '#808283',
    trigger_color: 'white',
    trigger_background_color: 'rgba(46, 49, 51, 0.6)'
}]);

// Identify the user and pass traits
// To enable, replace sample data with actual user traits and uncomment the line
UserVoice.push(['identify', {
    //email:      'john.doe@@example.com', // User’s email address
    //name:       'John Doe', // User’s real name
    //created_at: 1364406966, // Unix timestamp for the date the user signed up
    //id:         123, // Optional: Unique id of the user (if set, this should not change)
    //type:       'Owner', // Optional: segment your users by type
    //account: {
    //  id:           123, // Optional: associate multiple users with a single account
    //  name:         'Acme, Co.', // Account name
    //  created_at:   1364406966, // Unix timestamp for the date the account was created
    //  monthly_rate: 9.99, // Decimal; monthly rate of the account
    //  ltv:          1495.00, // Decimal; lifetime value of the account
    //  plan:         'Enhanced' // Plan name for the account
    //}
}]);

// Add default trigger to the bottom-right corner of the window:
UserVoice.push(['addTrigger', { mode: 'smartvote', trigger_position: 'bottom-right' }]);

// Or, use your own custom trigger:
//UserVoice.push(['addTrigger', '#id', { mode: 'smartvote' }]);

// Autoprompt for Satisfaction and SmartVote (only displayed under certain conditions)
UserVoice.push(['autoprompt', {}]);<commit_msg>fix emails not being displayed<commit_after>var refresh_ticker = function(){};

$(function(){
    var $ttemplate = $("#tickers-template");
    if ($ttemplate.length) {
        var template = Handlebars.compile($ttemplate.html());

        refresh_ticker = function(){
            API.ticker().success(function(res){
                var tickers = res.result;
                for (var i = 0; i < tickers.length; i++) {
                    if (Number(tickers[i].last) > Number(tickers[i].first)) {
                        tickers[i].color = "green";
                        tickers[i].icon = "chevron-up";
                    } else if (Number(tickers[i].last) < Number(tickers[i].first)) {
                        tickers[i].color = "red";
                        tickers[i].icon = "chevron-down";
                    } else {
                        tickers[i].color = "gray";
                        tickers[i].icon = "minus";
                    }
                    tickers[i].last = zerosTrim(tickers[i].last);
                }
                $('#ticker').html(template(tickers));
            }).error(function(){
                $.pnotify({
                    title: 'Failed to load ticker.',
                    text: 'Reason: ' + (JSON.parse(res.responseText)).error,
                    styling: 'bootstrap',
                    type: 'error',
                    text_escape: true
                });
            });
        };
        refresh_ticker();
    }
    showEmails();
});

function showEmails() {
    $('.hidden-email').each(function(idx, el) {
        var $el = $(el);
        $el.text($el.attr('data-name') + '@' + $el.attr('data-domain'));
        $el.attr('href', 'mailto:' + $el.text());
    });
}

function keepNumLength(m){
    var i = m.length - 1;
    while (m[i] == '0') {
        i--;
    }
    if (m[i] == '.') {
        i++;
    }
    return i;
}

function zerosToSpaces(m){
    m = Number(m).toFixed(8);
    var i = keepNumLength(m);
    return m.substring(0, i + 1) + Array(m.length - i).join('\xA0');
}

function zerosTrim(m){
    m = Number(m).toFixed(8);
    var i = keepNumLength(m);
    return m.substring(0, i + 1);
}

// Google analytics

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-54524343-1', 'auto');
ga('send', 'pageview');

// Include the UserVoice JavaScript SDK (only needed once on a page)
UserVoice=window.UserVoice||[];(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/xDR0gQn4m8VcCpTur0j4Q.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})();

//
// UserVoice Javascript SDK developer documentation:
// https://www.uservoice.com/o/javascript-sdk
//

// Set colors
UserVoice.push(['set', {
    accent_color: '#808283',
    trigger_color: 'white',
    trigger_background_color: 'rgba(46, 49, 51, 0.6)'
}]);

// Identify the user and pass traits
// To enable, replace sample data with actual user traits and uncomment the line
UserVoice.push(['identify', {
    //email:      'john.doe@@example.com', // User’s email address
    //name:       'John Doe', // User’s real name
    //created_at: 1364406966, // Unix timestamp for the date the user signed up
    //id:         123, // Optional: Unique id of the user (if set, this should not change)
    //type:       'Owner', // Optional: segment your users by type
    //account: {
    //  id:           123, // Optional: associate multiple users with a single account
    //  name:         'Acme, Co.', // Account name
    //  created_at:   1364406966, // Unix timestamp for the date the account was created
    //  monthly_rate: 9.99, // Decimal; monthly rate of the account
    //  ltv:          1495.00, // Decimal; lifetime value of the account
    //  plan:         'Enhanced' // Plan name for the account
    //}
}]);

// Add default trigger to the bottom-right corner of the window:
UserVoice.push(['addTrigger', { mode: 'smartvote', trigger_position: 'bottom-right' }]);

// Or, use your own custom trigger:
//UserVoice.push(['addTrigger', '#id', { mode: 'smartvote' }]);

// Autoprompt for Satisfaction and SmartVote (only displayed under certain conditions)
UserVoice.push(['autoprompt', {}]);<|endoftext|>


<commit_before>'use strict';

const preconditions = require('conditional');
const checkNotNull = preconditions.checkNotNull;
const checkArgument = preconditions.checkArgument;

const TokenProvider = require('./providers/token');
const LeaseManager = require('./lease-manager');
const onceWithTimeout = require('./utils/once-with-timeout');

// Default token secret
const DEFAULT_SECRET = '/v1/token/default';

// Default LeaseManager timeout
const DEFAULT_TIMEOUT_MILLIS = 500;

// LeaseManager status
const STATUS = {
  READY: 'READY',
  PENDING: 'PENDING'
};

class StorageService {

  /**
   * Create a new instance of a StorageService.
   *
   * @param {object} options - configuration for the default TokenProvider.
   *
   */
  constructor(options) {
    options = options || {};
    this.timeout = options.timeout || DEFAULT_TIMEOUT_MILLIS;

    this._managers = new Map();
    this._getLeaseManager(DEFAULT_SECRET);
  }

  lookup(secret, callback) {
    const manager = this._getLeaseManager(secret);

    if (manager.status === STATUS.READY) {

      // LeaseManager is ready, immediately callback with data
      callback(null, manager.data);
    } else {

      // Queue the callback with a timeout
      onceWithTimeout(manager, 'ready', this.timeout)
        .then(() => callback(null, manager.data))
        .catch((err) => callback(err, null));
    }
  }

  /**
   * Gets a LeaseManager for the given secret, or provisions a new LeaseManager
   * if it is not already provisioned.
   *
   * @private
   * @param {String} secret - the secret to get (or provision) a LeaseManager for.
   * @returns {LeaseManager}
   *
   */
  _getLeaseManager(secret) {

    // If the LeaseManager is already provisioned, return it
    if(this._managers.has(secret)) {
      return this._managers.get(secret);
    }

    // Otherwise, provision a new LeaseManager for the secret
    // TODO: get provider configuration (options) using secret
    // TODO: determine provider type from secret
    const options = {};
    const provider = new TokenProvider(options);
    const manager = new LeaseManager(provider);

    // Initialize the LeaseManager
    manager.initialize();

    this._managers.set(secret, manager);

    return manager;
  }
}

module.exports = StorageService;
<commit_msg>PD-6772 Removed unused imports.<commit_after>'use strict';

const TokenProvider = require('./providers/token');
const LeaseManager = require('./lease-manager');
const onceWithTimeout = require('./utils/once-with-timeout');

// Default token secret
const DEFAULT_SECRET = '/v1/token/default';

// Default LeaseManager timeout
const DEFAULT_TIMEOUT_MILLIS = 500;

// LeaseManager status
const STATUS = {
  READY: 'READY',
  PENDING: 'PENDING'
};

class StorageService {

  /**
   * Create a new instance of a StorageService.
   *
   * @param {object} options - configuration for the default TokenProvider.
   *
   */
  constructor(options) {
    options = options || {};
    this.timeout = options.timeout || DEFAULT_TIMEOUT_MILLIS;

    this._managers = new Map();
    this._getLeaseManager(DEFAULT_SECRET);
  }

  lookup(secret, callback) {
    const manager = this._getLeaseManager(secret);

    if (manager.status === STATUS.READY) {

      // LeaseManager is ready, immediately callback with data
      callback(null, manager.data);
    } else {

      // Queue the callback with a timeout
      onceWithTimeout(manager, 'ready', this.timeout)
        .then(() => callback(null, manager.data))
        .catch((err) => callback(err, null));
    }
  }

  /**
   * Gets a LeaseManager for the given secret, or provisions a new LeaseManager
   * if it is not already provisioned.
   *
   * @private
   * @param {String} secret - the secret to get (or provision) a LeaseManager for.
   * @returns {LeaseManager}
   *
   */
  _getLeaseManager(secret) {

    // If the LeaseManager is already provisioned, return it
    if(this._managers.has(secret)) {
      return this._managers.get(secret);
    }

    // Otherwise, provision a new LeaseManager for the secret
    // TODO: get provider configuration (options) using secret
    // TODO: determine provider type from secret
    const options = {};
    const provider = new TokenProvider(options);
    const manager = new LeaseManager(provider);

    // Initialize the LeaseManager
    manager.initialize();

    this._managers.set(secret, manager);

    return manager;
  }
}

module.exports = StorageService;
<|endoftext|>


<commit_before>import { distance } from '../../common/geometry'
import PolarMachine from './PolarMachine'
import RectMachine from './RectMachine'
import Victor from 'victor'
import ReactGA from 'react-ga'
import { evaluate } from 'mathjs'
import throttle from 'lodash/throttle'

// Transform functions
function rotate(vertex, angleDeg) {
  const angle = Math.PI / 180.0 * angleDeg
  return new Victor(
   vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle),
   vertex.x * Math.sin(angle) + vertex.y * Math.cos(angle)
  )
}

function scale(vertex, scalePerc) {
  const scale = scalePerc / 100.0
  return new Victor(
    vertex.x * scale,
    vertex.y * scale
  )
}

function offset(vertex, offsetX, offsetY) {
  return new Victor(
    vertex.x + offsetX,
    vertex.y + offsetY
  )
}

function track(vertex, data, loopIndex) {
  const angle = data.trackLength * loopIndex / 16 * 2.0 * Math.PI
  let radius = 1.0

  if (data.trackGrowEnabled) {
    radius = 1.0 + loopIndex / 10.0 * data.trackGrow / 100.0
  }
  return new Victor(
    vertex.x + radius * data.trackValue * Math.cos(angle),
    vertex.y + radius * data.trackValue * Math.sin(angle)
  )
}

export const transformShape = (data, vertex, amount, trackIndex=0, numLoops) => {
  numLoops = numLoops || data.numLoops
  let transformedVertex = vertex

  if (data.repeatEnabled && data.growEnabled) {
    transformedVertex = scale(transformedVertex, 100.0 + (data.growValue * amount))
  }

  transformedVertex = offset(transformedVertex, data.offsetX || 0, data.offsetY || 0)

  if (data.repeatEnabled && data.spinEnabled) {
    const loopPeriod = numLoops / (parseInt(data.spinSwitchbacks) + 1)
    const stage = amount/loopPeriod
    const direction = (stage % 2 < 1 ? 1.0 : -1.0)
    var spinAmount = evaluate(data.spinValue, {i: amount})

    // Add in the amount it goes positive to the negatives, so they start at the same place.
    if (direction < 0.0) {
      spinAmount += loopPeriod * data.spinValue
    }
    transformedVertex = rotate(transformedVertex, spinAmount)
  }

  if (data.repeatEnabled && data.trackEnabled) {
    transformedVertex = track(transformedVertex, data, trackIndex)
  }

  return transformedVertex
}

function buildTrackLoop(vertices, transform, i, t) {
  const numTrackLoops = transform.repeatEnabled ? transform.trackNumLoops : 1
  const nextTrackVertex = transformShape(transform, vertices[0], 0, i + 1, numTrackLoops)
  const backtrack = numTrackLoops > 1 && t === numTrackLoops - 1
  let numVertices = vertices.length
  let trackVertices = []
  let trackDistances = []

  for (var j=0; j<numVertices; j++) {
    const amount = transform.transformMethod === 'smear' ? i + t + j/vertices.length : i + t
    const trackVertex = transformShape(transform, vertices[j], amount, i, numTrackLoops)
    trackVertices.push(trackVertex)

    if (backtrack) {
      trackDistances.push(distance(nextTrackVertex, trackVertex))
    }
  }

  // backtrack to the vertex with the shortest distance to the first vertex in
  // the next track loop; this minimizes the amount our shape draws over the
  // previous shape, which is not visually appealing.
  if (backtrack) {
    let minIdx = 0
    let minD = Number.MAX_SAFE_INTEGER

    trackDistances.forEach((d, idx) => {
      if (d <= minD) {
        minD = d
        minIdx = idx
      }
    })

    if (minIdx !== 0) {
      trackVertices = trackVertices.concat(trackVertices.slice(minIdx, trackVertices.length-1).reverse())
    }
  }

  return trackVertices
}

// ensure vertices do not exceed machine boundary limits, and endpoints as needed
export const polishVertices = (vertices, machine) => {
  vertices = vertices.map(vertex => Victor.fromObject(vertex))
  const machineClass = machine.rectangular ? RectMachine : PolarMachine

  if (vertices.length > 0) {
    vertices = new machineClass(vertices, machine).polish().vertices
  }

  return vertices
}

export const scaleImportedVertices = (importer, machine) => {
  var x_scale = (machine.maxX - machine.minX)/2.0 * 0.01 * importer.zoom
  var y_scale = (machine.maxY - machine.minY)/2.0 * 0.01 * importer.zoom

  if (!machine.rectangular) {
    x_scale = y_scale = machine.maxRadius * 0.01 * importer.zoom
  }

  if (importer.aspectRatio) {
    const machine_aspect_ratio = y_scale / x_scale
    if (importer.originalAspectRatio > machine_aspect_ratio) {
      x_scale = x_scale / importer.originalAspectRatio * machine_aspect_ratio
    } else {
      y_scale = y_scale * importer.originalAspectRatio / machine_aspect_ratio
    }
  }

  return importer.vertices.map( (vertex) => {
    return {...vertex,
      x: vertex.x * x_scale,
      y: vertex.y * y_scale,
    }
  })
}

function reportTiming(time) {
  time = Math.max(time, 0.01)
  ReactGA.timing({
    category: 'Compute',
    variable: 'transformShapes',
    value: time, // in milliseconds
  });
}

const throttledReportTiming = throttle(reportTiming, 1000, {trailing: true })

export const transformShapes = (vertices, transform) => {
  const startTime = performance.now()
  const numLoops = transform.repeatEnabled ? transform.numLoops : 1
  const numTrackLoops = transform.repeatEnabled ? transform.trackNumLoops : 1
  let outputVertices = []

  let scaledVertices = vertices.map(vertex => {
    return scale(vertex, 100.0 * transform.startingSize)
  })

  if (transform.transformMethod === 'smear' && transform.repeatEnabled) {
    // remove last vertex; we don't want to return to our starting point when completing the shape
    scaledVertices.pop()
  }

  if (transform.trackEnabled && numTrackLoops > 1) {
    for (var i=0; i<numLoops; i++) {
      for (var t=0; t<numTrackLoops; t++) {
        outputVertices = outputVertices.concat(buildTrackLoop(scaledVertices, transform, i, t))
      }
    }
  } else {
    for (i=0; i<numLoops; i++) {
      for (var j=0; j<scaledVertices.length; j++) {
        let amount = transform.transformMethod === 'smear' ? i + j/scaledVertices.length : i
        outputVertices.push(transformShape(transform, scaledVertices[j], amount, amount))
      }
    }
  }

  const endTime = performance.now()
  throttledReportTiming(endTime - startTime)
  return outputVertices
}
<commit_msg>Compute vertices using these math functions.<commit_after>import { distance } from '../../common/geometry'
import PolarMachine from './PolarMachine'
import RectMachine from './RectMachine'
import Victor from 'victor'
import ReactGA from 'react-ga'
import { evaluate } from 'mathjs'
import throttle from 'lodash/throttle'

// Transform functions
function rotate(vertex, angleDeg) {
  const angle = Math.PI / 180.0 * angleDeg
  return new Victor(
   vertex.x * Math.cos(angle) - vertex.y * Math.sin(angle),
   vertex.x * Math.sin(angle) + vertex.y * Math.cos(angle)
  )
}

function scale(vertex, scalePerc) {
  const scale = scalePerc / 100.0
  return new Victor(
    vertex.x * scale,
    vertex.y * scale
  )
}

function offset(vertex, offsetX, offsetY) {
  return new Victor(
    vertex.x + offsetX,
    vertex.y + offsetY
  )
}

function track(vertex, data, loopIndex) {
  const angle = data.trackLength * loopIndex / 16 * 2.0 * Math.PI
  let radius = 1.0

  if (data.trackGrowEnabled) {
    radius = 1.0 + loopIndex / 10.0 * data.trackGrow / 100.0
  }
  return new Victor(
    vertex.x + radius * data.trackValue * Math.cos(angle),
    vertex.y + radius * data.trackValue * Math.sin(angle)
  )
}

export const transformShape = (data, vertex, amount, trackIndex=0, numLoops) => {
  numLoops = numLoops || data.numLoops
  let transformedVertex = vertex

  if (data.repeatEnabled && data.growEnabled) {
    var growAmount = 100
    if (data.growAdvanced) {
      try {
        growAmount = data.growValue * evaluate(data.growMath, {i: amount})
      }
      catch (err) {
        console.log(err)
        growAmount = 200
      }
    } else {
      growAmount = 100.0 + (data.growValue * amount)
    }
    transformedVertex = scale(transformedVertex, growAmount)
  }

  transformedVertex = offset(transformedVertex, data.offsetX || 0, data.offsetY || 0)

  if (data.repeatEnabled && data.spinEnabled) {
    var spinAmount = 0
    if (data.spinAdvanced) {
      try {
        spinAmount = evaluate(data.spinMath, {i: amount})
      }
      catch (err) {
        console.log(err)
        spinAmount = 0
      }
    } else {
      const loopPeriod = numLoops / (parseInt(data.spinSwitchbacks) + 1)
      const stage = amount/loopPeriod
      const direction = (stage % 2 < 1 ? 1.0 : -1.0)
      spinAmount = direction * (amount % loopPeriod) * data.spinValue
      // Add in the amount it goes positive to the negatives, so they start at the same place.
      if (direction < 0.0) {
        spinAmount += loopPeriod * data.spinValue
      }
    }
    transformedVertex = rotate(transformedVertex, spinAmount)
  }

  if (data.repeatEnabled && data.trackEnabled) {
    transformedVertex = track(transformedVertex, data, trackIndex)
  }

  return transformedVertex
}

function buildTrackLoop(vertices, transform, i, t) {
  const numTrackLoops = transform.repeatEnabled ? transform.trackNumLoops : 1
  const nextTrackVertex = transformShape(transform, vertices[0], 0, i + 1, numTrackLoops)
  const backtrack = numTrackLoops > 1 && t === numTrackLoops - 1
  let numVertices = vertices.length
  let trackVertices = []
  let trackDistances = []

  for (var j=0; j<numVertices; j++) {
    const amount = transform.transformMethod === 'smear' ? i + t + j/vertices.length : i + t
    const trackVertex = transformShape(transform, vertices[j], amount, i, numTrackLoops)
    trackVertices.push(trackVertex)

    if (backtrack) {
      trackDistances.push(distance(nextTrackVertex, trackVertex))
    }
  }

  // backtrack to the vertex with the shortest distance to the first vertex in
  // the next track loop; this minimizes the amount our shape draws over the
  // previous shape, which is not visually appealing.
  if (backtrack) {
    let minIdx = 0
    let minD = Number.MAX_SAFE_INTEGER

    trackDistances.forEach((d, idx) => {
      if (d <= minD) {
        minD = d
        minIdx = idx
      }
    })

    if (minIdx !== 0) {
      trackVertices = trackVertices.concat(trackVertices.slice(minIdx, trackVertices.length-1).reverse())
    }
  }

  return trackVertices
}

// ensure vertices do not exceed machine boundary limits, and endpoints as needed
export const polishVertices = (vertices, machine) => {
  vertices = vertices.map(vertex => Victor.fromObject(vertex))
  const machineClass = machine.rectangular ? RectMachine : PolarMachine

  if (vertices.length > 0) {
    vertices = new machineClass(vertices, machine).polish().vertices
  }

  return vertices
}

export const scaleImportedVertices = (importer, machine) => {
  var x_scale = (machine.maxX - machine.minX)/2.0 * 0.01 * importer.zoom
  var y_scale = (machine.maxY - machine.minY)/2.0 * 0.01 * importer.zoom

  if (!machine.rectangular) {
    x_scale = y_scale = machine.maxRadius * 0.01 * importer.zoom
  }

  if (importer.aspectRatio) {
    const machine_aspect_ratio = y_scale / x_scale
    if (importer.originalAspectRatio > machine_aspect_ratio) {
      x_scale = x_scale / importer.originalAspectRatio * machine_aspect_ratio
    } else {
      y_scale = y_scale * importer.originalAspectRatio / machine_aspect_ratio
    }
  }

  return importer.vertices.map( (vertex) => {
    return {...vertex,
      x: vertex.x * x_scale,
      y: vertex.y * y_scale,
    }
  })
}

function reportTiming(time) {
  time = Math.max(time, 0.01)
  ReactGA.timing({
    category: 'Compute',
    variable: 'transformShapes',
    value: time, // in milliseconds
  });
}

const throttledReportTiming = throttle(reportTiming, 1000, {trailing: true })

export const transformShapes = (vertices, transform) => {
  const startTime = performance.now()
  const numLoops = transform.repeatEnabled ? transform.numLoops : 1
  const numTrackLoops = transform.repeatEnabled ? transform.trackNumLoops : 1
  let outputVertices = []

  let scaledVertices = vertices.map(vertex => {
    return scale(vertex, 100.0 * transform.startingSize)
  })

  if (transform.transformMethod === 'smear' && transform.repeatEnabled) {
    // remove last vertex; we don't want to return to our starting point when completing the shape
    scaledVertices.pop()
  }

  if (transform.trackEnabled && numTrackLoops > 1) {
    for (var i=0; i<numLoops; i++) {
      for (var t=0; t<numTrackLoops; t++) {
        outputVertices = outputVertices.concat(buildTrackLoop(scaledVertices, transform, i, t))
      }
    }
  } else {
    for (i=0; i<numLoops; i++) {
      for (var j=0; j<scaledVertices.length; j++) {
        let amount = transform.transformMethod === 'smear' ? i + j/scaledVertices.length : i
        outputVertices.push(transformShape(transform, scaledVertices[j], amount, amount))
      }
    }
  }

  const endTime = performance.now()
  throttledReportTiming(endTime - startTime)
  return outputVertices
}
<|endoftext|>


<commit_before>'use strict';

var qs = require('qs');

/**
 * Module exports.
 */
module.exports = mock;

/**
 * Installs the `mock` extension to superagent.
 */
function mock (superagent, config) {
  var Request = superagent.Request;
  var parsers = [];

  /**
   * Keep the default methods
   */
  var oldGet = superagent.get;
  var oldPost = superagent.post;
  var oldEnd = Request.prototype.end;

  /**
   * Attempt to match url against the patterns in the fixtures.
   */
  function testUrlForPatterns(url) {
    var match = config.filter(function (parser) {
      return new RegExp(parser.pattern, 'g').test(url);
    })[0] || null;

    if (match) {
      parsers[url] = match;
    }
  }

  /**
   * Override get function
   */
  superagent.get = function (url, data, fn) {
    testUrlForPatterns(url);
    var req;
    if (parsers[url]) {
      req = superagent('GET', url, data, fn);
    } else {
      req = oldGet.call(this, url, data, fn);
    }
    return req;
  };

  /**
   * Override post function
   */
   superagent.post = function (url, data, fn) {
     testUrlForPatterns(url);
     return parsers[url] ? superagent('POST', url, data, fn) : oldPost.call(this, url, data, fn);
   };

  /**
   * Override end function
   */
  Request.prototype.end = function (fn) {

    var path = this.url;
    var querystring = '';

    if (this._query) {
      querystring += this._query.join('&');
    } else {
      if (this.qs) {
        querystring += qs.stringify(this.qs);
      }
      if (this.qsRaw) {
        querystring += this.qsRaw.join('&');
      }
    }

    if (querystring.length) {
      path += (~path.indexOf('?') ? '&' : '?') + querystring;
    }

    var parser = parsers[this.url];

    if (parser) {
      var match = new RegExp(parser.pattern, 'g').exec(path);

      try {
        var fixtures = parser.fixtures(match);
        fn(null, parsers[this.url].callback(match, fixtures));
      } catch(err) {
        fn(err, undefined);
      }
    } else {
      oldEnd.call(this, fn);
    }
  };
}
<commit_msg>Corrected Linter Errors.<commit_after>'use strict';

var qs = require('qs');

/**
 * Module exports.
 */
module.exports = mock;

/**
 * Installs the `mock` extension to superagent.
 */
function mock (superagent, config) {
  var Request = superagent.Request;
  var parsers = [];

  /**
   * Keep the default methods
   */
  var oldGet = superagent.get;
  var oldPost = superagent.post;
  var oldEnd = Request.prototype.end;

  /**
   * Attempt to match url against the patterns in the fixtures.
   */
  function testUrlForPatterns(url) {
    var match = config.filter(function (parser) {
      return new RegExp(parser.pattern, 'g').test(url);
    })[0] || null;

    if (match) {
      parsers[url] = match;
    }
  }

  /**
   * Override get function
   */
  superagent.get = function (url, data, fn) {
    testUrlForPatterns(url);
    var req;
    if (parsers[url]) {
      req = superagent('GET', url, data, fn);
    } else {
      req = oldGet.call(this, url, data, fn);
    }
    return req;
  };

  /**
   * Override post function
   */
   superagent.post = function (url, data, fn) {
     testUrlForPatterns(url);
     return parsers[url] ? superagent('POST', url, data, fn) : oldPost.call(this, url, data, fn);
   };

  /**
   * Override end function
   */
  Request.prototype.end = function (fn) {

    var path = this.url;
    var querystring = '';

    if (this._query) {
      querystring += this._query.join('&');
    } else {
      if (this.qs) {
        querystring += qs.stringify(this.qs);
      }
      if (this.qsRaw) {
        querystring += this.qsRaw.join('&');
      }
    }

    if (querystring.length) {
      path += (~path.indexOf('?') ? '&' : '?') + querystring;
    }

    var parser = parsers[this.url];

    if (parser) {
      var match = new RegExp(parser.pattern, 'g').exec(path);

      try {
        var fixtures = parser.fixtures(match);
        fn(null, parsers[this.url].callback(match, fixtures));
      } catch(err) {
        fn(err, undefined);
      }
    } else {
      oldEnd.call(this, fn);
    }
  };
}
<|endoftext|>


<commit_before>var http = require("http");
var url = require("url");
var jsdom = require("jsdom").jsdom();
var fs = require("fs");
var querystring = require("querystring");
var exec = require("child_process").exec;

var decoder_configuration = require("./decoder_configuration.js");
	// options.secret_key_base = process.env.USER;	//we can set here secret_key_base
var cookieDecoder = require("cookieDecoder")(decoder_configuration);
var ChartsMap = require("./service.js")();	//TODO - zienic zeby modul eksportowal mape a nie funkcje, albo przekazywac handlery
var DataRetriever = require("./data_retriever.js");

var jade = require("jade");
var locals = require("./config.js");

var PORT = 8080,
	EXTERNAL_IP = "172.16.67.121",			//TODO - retrieve external IP
	ADDRESS = EXTERNAL_IP + "/chart";		//address suffix set in /etc/nginx/conf.d/default.conf

var app = http.createServer(function(req, res) {
	var parsedUrl = url.parse(req.url);
	var pathname = parsedUrl.pathname;
	var parameters = querystring.parse(parsedUrl.query);
	console.log("Pathname: " + pathname);
	console.log("Parameters: " + JSON.stringify(parameters));

	//TODO - jak nie tworzyc mapy za kazdym razem? -> wystarczy zmieniac tylko map["/panel"] ALBO parameters jako dodatkowy argument map[...](..., parameters)
	var map = prepare_map_with_requests(parameters);

	var path = pathname.split("/")[1];
	if(map[path]){
		map[path](req, res, pathname);
	}
	else if(ChartsMap["/"+path]) {
		ChartsMap["/"+path](parameters, function(object) {
			authenticate(req.headers.cookie, parameters["id"], function(data) {
				console.log("OK! Successfully authorized.");
				res.writeHead(200, {'Content-Type': 'text/plain'});
				var output = jade.renderFile("."+pathname+pathname+"Chart.jade", parameters);
				output += object.content;
				res.write(output);
				res.end();
			}, function(err) {
				console.log("FAILED! Sending info about error to Scalarm... \n" + err);
				res.write("Unable to authenticate");
				res.end();
			});
		}, function(err) {
			res.write(err);
			res.end();
			return;
		});
	}
	else {	//when URL doesn't match => incorrect request
		res.writeHead(404);
		res.write(pathname + " : incorrect request!");
		res.end();
	}
})
DataRetriever.connect(function(){
	app.listen(PORT, function(){
		console.log(new Date() + " Listening on port " + PORT);
	});
}, function(){
	console.log("Connection to database failed");
	throw new Error("Connection to database failed");
})

var WebSocketServer = require('websocket').server;
wsServer = new WebSocketServer({
	httpServer: app
})

wsServer.on('request', function(request) {
	//TODO -- authentication!
	console.log(new Date() + " Connection from origin " + request.origin + ".");
	var experimentID = request.httpRequest.url.slice(1);
	authenticate(request.httpRequest.headers.cookie, experimentID, function(data) {
		console.log("OK! Successfully authorized.");
		var connection = request.accept(null, request.origin);
		console.log(new Date() + " Connection accepted.");

		connection.on('close', function(connection) {
	        console.log(new Date() + " Connection closed");
	        //TODO - close cursor?
	    });

		DataRetriever.createStreamFor(connection, experimentID);
	}, function(err) {
		console.log("Authentication failed! \n" + err);
	});
});

//--------------------------------
function authenticate(cookie, experimentID, success, error){
	//what if there are more cookies??
	var cookieGood = cookie.substr(17, cookie.length); //MAGIC NUMBER! :D (just for remove _scalarm_session= from the beginning)
	var output = cookieDecoder(cookieGood);

	exec("ruby serialized_object_to_json.rb " + new Buffer(output).toString("base64"), function(err, stdout, stderr) {
	    if (err !== null) {
	    	console.log('stderr: ' + stderr);
	    	console.log('exec error: ' + err);
	    	error(err);
	    	return;
	    }

		var userID = JSON.parse(stdout)["user"];
		console.log("\tuserID: ", userID);
		console.log("\texperimentID: ", experimentID);

		DataRetriever.authenticate(userID, experimentID, function(dataSuccess) {
			success(dataSuccess);
		}, function(dataError) {
			error(dataError);
		});
	});
}

function prepare_script_and_style_tags(typeOfChart) {
	var tags = {};
	tags.script_tag_main = jsdom.createElement("script");
	tags.script_tag_main.setAttribute("type", "text/javascript");
	tags.script_tag_main.setAttribute("src","//" + ADDRESS +"/main"+ typeOfChart);

	for(var prop in tags) {
		tags[prop] = tags[prop].outerHTML;
	}

    return tags;
}

function prepare_map_with_requests(parameters) {
	var map = {};
	map["panel"] = function(req, res){
		DataRetriever.getParameters(parameters["id"], function(data) {
			locals.parameters = data.parameters;
			locals.output = data.result;
			locals.address = ADDRESS;
			res.writeHead(200);
			var panel = jade.renderFile("panel.jade", locals);
			res.write(panel);
			res.end();
		},
		function(err) {
			res.writeHead(404);
			res.write("Error getting parameters\n");
			res.write(err+"\n");
			res.end();
		})
	};

	map["images"] = function(req, res, pathname) {
		fs.readFile('.'+pathname, function(error, data) {
			if(error) {
				res.writeHead(404);
				res.write("File " + file_path + " : not found!\n");
				res.write(error.toString());
				res.end();
			}
			else {
				res.writeHead(200);
				res.write(data);
				res.end();
			}
		});
	};

	map["main"] = function(req, res, pathname){
		var type = pathname.split("/")[2];
		var resource = pathname.split("/")[1];

		var file_path = type+"/"+type+"_chart_"+resource;
		file_path += resource==="style" ? ".css" : ".js";

		fs.readFile(file_path, function(error, data) {
			if(error) {
				res.writeHead(404);
				res.write("File " + file_path + " : not found!\n");
				res.write(error.toString());
				res.end();
				return;
			}
			else {
				res.writeHead(200);
				res.write(data);
				res.end();
				return;
			}
		});
	};

	map["scripts"] = function(req, res, pathname){
		var chart_type = pathname.split("/")[2];
		var tags = prepare_script_and_style_tags("/"+chart_type);

		res.writeHead(200);
		res.write(tags.script_tag_main);
		res.end();
	};

	return map;
}
<commit_msg>Added /status url<commit_after>var http = require("http");
var url = require("url");
var jsdom = require("jsdom").jsdom();
var fs = require("fs");
var querystring = require("querystring");
var exec = require("child_process").exec;

var decoder_configuration = require("./decoder_configuration.js");
	// options.secret_key_base = process.env.USER;	//we can set here secret_key_base
var cookieDecoder = require("cookieDecoder")(decoder_configuration);
var ChartsMap = require("./service.js")();	//TODO - zienic zeby modul eksportowal mape a nie funkcje, albo przekazywac handlery
var DataRetriever = require("./data_retriever.js");

var jade = require("jade");
var locals = require("./config.js");

var PORT = 8080,
	EXTERNAL_IP = "172.16.67.121",			//TODO - retrieve external IP
	ADDRESS = EXTERNAL_IP + "/chart";		//address suffix set in /etc/nginx/conf.d/default.conf

var app = http.createServer(function(req, res) {
	var parsedUrl = url.parse(req.url);
	var pathname = parsedUrl.pathname;
	var parameters = querystring.parse(parsedUrl.query);
	console.log("Pathname: " + pathname);
	console.log("Parameters: " + JSON.stringify(parameters));

	//TODO - jak nie tworzyc mapy za kazdym razem? -> wystarczy zmieniac tylko map["/panel"] ALBO parameters jako dodatkowy argument map[...](..., parameters)
	var map = prepare_map_with_requests(parameters);

	var path = pathname.split("/")[1];

	if(path==="status"){
		res.write("status ok");
		res.end();
	}
	else if(map[path]){
		map[path](req, res, pathname);
	}
	else if(ChartsMap["/"+path]) {
		ChartsMap["/"+path](parameters, function(object) {
			authenticate(req.headers.cookie, parameters["id"], function(data) {
				console.log("OK! Successfully authorized.");
				res.writeHead(200, {'Content-Type': 'text/plain'});
				var output = jade.renderFile("."+pathname+pathname+"Chart.jade", parameters);
				output += object.content;
				res.write(output);
				res.end();
			}, function(err) {
				console.log("FAILED! Sending info about error to Scalarm... \n" + err);
				res.write("Unable to authenticate");
				res.end();
			});
		}, function(err) {
			res.write(err);
			res.end();
			return;
		});
	}
	else {	//when URL doesn't match => incorrect request
		res.writeHead(404);
		res.write(pathname + " : incorrect request!");
		res.end();
	}
})
DataRetriever.connect(function(){
	app.listen(PORT, function(){
		console.log(new Date() + " Listening on port " + PORT);
	});
}, function(){
	console.log("Connection to database failed");
	throw new Error("Connection to database failed");
})

var WebSocketServer = require('websocket').server;
wsServer = new WebSocketServer({
	httpServer: app
})

wsServer.on('request', function(request) {
	//TODO -- authentication!
	console.log(new Date() + " Connection from origin " + request.origin + ".");
	var experimentID = request.httpRequest.url.slice(1);
	authenticate(request.httpRequest.headers.cookie, experimentID, function(data) {
		console.log("OK! Successfully authorized.");
		var connection = request.accept(null, request.origin);
		console.log(new Date() + " Connection accepted.");

		connection.on('close', function(connection) {
	        console.log(new Date() + " Connection closed");
	        //TODO - close cursor?
	    });

		DataRetriever.createStreamFor(connection, experimentID);
	}, function(err) {
		console.log("Authentication failed! \n" + err);
	});
});

//--------------------------------
function authenticate(cookie, experimentID, success, error){
	//what if there are more cookies??
	var cookieGood = cookie.substr(17, cookie.length); //MAGIC NUMBER! :D (just for remove _scalarm_session= from the beginning)
	var output = cookieDecoder(cookieGood);

	exec("ruby serialized_object_to_json.rb " + new Buffer(output).toString("base64"), function(err, stdout, stderr) {
	    if (err !== null) {
	    	console.log('stderr: ' + stderr);
	    	console.log('exec error: ' + err);
	    	error(err);
	    	return;
	    }

		var userID = JSON.parse(stdout)["user"];
		console.log("\tuserID: ", userID);
		console.log("\texperimentID: ", experimentID);

		DataRetriever.authenticate(userID, experimentID, function(dataSuccess) {
			success(dataSuccess);
		}, function(dataError) {
			error(dataError);
		});
	});
}

function prepare_script_and_style_tags(typeOfChart) {
	var tags = {};
	tags.script_tag_main = jsdom.createElement("script");
	tags.script_tag_main.setAttribute("type", "text/javascript");
	tags.script_tag_main.setAttribute("src","//" + ADDRESS +"/main"+ typeOfChart);

	for(var prop in tags) {
		tags[prop] = tags[prop].outerHTML;
	}

    return tags;
}

function prepare_map_with_requests(parameters) {
	var map = {};
	map["panel"] = function(req, res){
		DataRetriever.getParameters(parameters["id"], function(data) {
			locals.parameters = data.parameters;
			locals.output = data.result;
			locals.address = ADDRESS;
			res.writeHead(200);
			var panel = jade.renderFile("panel.jade", locals);
			res.write(panel);
			res.end();
		},
		function(err) {
			res.writeHead(404);
			res.write("Error getting parameters\n");
			res.write(err+"\n");
			res.end();
		})
	};

	map["images"] = function(req, res, pathname) {
		fs.readFile('.'+pathname, function(error, data) {
			if(error) {
				res.writeHead(404);
				res.write("File " + file_path + " : not found!\n");
				res.write(error.toString());
				res.end();
			}
			else {
				res.writeHead(200);
				res.write(data);
				res.end();
			}
		});
	};

	map["main"] = function(req, res, pathname){
		var type = pathname.split("/")[2];
		var resource = pathname.split("/")[1];

		var file_path = type+"/"+type+"_chart_"+resource;
		file_path += resource==="style" ? ".css" : ".js";

		fs.readFile(file_path, function(error, data) {
			if(error) {
				res.writeHead(404);
				res.write("File " + file_path + " : not found!\n");
				res.write(error.toString());
				res.end();
				return;
			}
			else {
				res.writeHead(200);
				res.write(data);
				res.end();
				return;
			}
		});
	};

	map["scripts"] = function(req, res, pathname){
		var chart_type = pathname.split("/")[2];
		var tags = prepare_script_and_style_tags("/"+chart_type);

		res.writeHead(200);
		res.write(tags.script_tag_main);
		res.end();
	};

	return map;
}
<|endoftext|>