<commit_before>import _ from 'lodash';
import path from 'path';
import url from 'url';
import logger from './logger';
import _fs from 'fs';
import B from 'bluebird';
import { tempDir, fs, util, zip } from 'appium-support';
import request from 'request';
import asyncRequest from 'request-promise';
import LRU from 'lru-cache';
import AsyncLock from 'async-lock';
import sanitize from 'sanitize-filename';

const ZIP_EXTS = ['.zip', '.ipa'];
const ZIP_MIME_TYPES = [
  'application/zip',
  'application/x-zip-compressed',
  'multipart/x-zip',
];
const APPLICATIONS_CACHE = new LRU({
  max: 100,
});
const APPLICATIONS_CACHE_GUARD = new AsyncLock();
const SANITIZE_REPLACEMENT = '-';

async function retrieveHeaders (link) {
  try {
    const response = await asyncRequest({
      url: link,
      method: 'HEAD',
      resolveWithFullResponse: true,
      timeout: 5000,
    });
    return response.headers;
  } catch (e) {
    logger.debug(`Cannot send HEAD request to '${link}'. Original error: ${e.message}`);
  }
  return {};
}

function getCachedApplicationPath (link, currentModified) {
  if (!APPLICATIONS_CACHE.has(link) || !currentModified) {
    return null;
  }

  const {lastModified, fullPath} = APPLICATIONS_CACHE.get(link);
  if (lastModified && currentModified.getTime() <= lastModified.getTime()) {
    logger.debug(`Reusing already downloaded application at '${fullPath}'`);
    return fullPath;
  }
  logger.debug(`'Last-Modified' timestamp of '${link}' has been updated. ` +
    `An updated copy of the application is going to be downloaded.`);
  return null;
}

function verifyAppExtension (app, supportedAppExtensions) {
  if (supportedAppExtensions.includes(path.extname(app))) {
    return app;
  }
  throw new Error(`New app path '${app}' did not have extension(s) '${supportedAppExtensions}'`);
}

async function configureApp (app, supportedAppExtensions) {
  if (!_.isString(app)) {
    // immediately shortcircuit if not given an app
    return;
  }
  if (!_.isArray(supportedAppExtensions)) {
    supportedAppExtensions = [supportedAppExtensions];
  }

  let newApp = app;
  let shouldUnzipApp = false;
  let archiveHash = null;
  let currentModified = null;
  const {protocol} = url.parse(newApp);
  const isUrl = ['http:', 'https:'].includes(protocol);

  return await APPLICATIONS_CACHE_GUARD.acquire(app, async () => {
    if (isUrl) {
      // Use the app from remote URL
      logger.info(`Using downloadable app '${newApp}'`);
      const headers = await retrieveHeaders(newApp);
      if (headers['last-modified']) {
        logger.debug(`Last-Modified: ${headers['last-modified']}`);
        currentModified = new Date(headers['last-modified']);
      }
      const cachedPath = getCachedApplicationPath(app, currentModified);
      if (cachedPath) {
        if (await fs.exists(cachedPath)) {
          logger.info(`Reusing the previously downloaded application at '${cachedPath}'`);
          return verifyAppExtension(cachedPath, supportedAppExtensions);
        }
        logger.info(`The application at '${cachedPath}' does not exist anymore. Deleting it from the cache`);
        APPLICATIONS_CACHE.del(app);
      }

      let fileName = null;
      // to determine if we need to unzip the app, we have a number of places
      // to look: content type, content disposition, or the file extension
      if (ZIP_EXTS.includes(path.extname(newApp))) {
        fileName = sanitize(path.basename(newApp), {
          replacement: SANITIZE_REPLACEMENT
        });
        shouldUnzipApp = true;
      }
      if (headers['content-type']) {
        logger.debug(`Content-Type: ${headers['content-type']}`);
        // the filetype may not be obvious for certain urls, so check the mime type too
        if (ZIP_MIME_TYPES.includes(headers['content-type'])) {
          if (!fileName) {
            fileName = 'appium-app.zip';
          }
          shouldUnzipApp = true;
        }
      }
      if (headers['content-disposition'] && /^attachment/i.test(headers['content-disposition'])) {
        const match = /filename="([^"]+)/i.exec(headers['content-disposition']);
        if (match) {
          logger.debug(`Parsed file name '${match[1]}' from 'Content-Disposition' header`);
          fileName = sanitize(match[1], {
            replacement: SANITIZE_REPLACEMENT
          });
          shouldUnzipApp = shouldUnzipApp || ZIP_EXTS.includes(path.extname(fileName));
        }
      }
      if (!fileName) {
        // assign the default extension if none has been detected
        fileName = `appium-app${_.first(supportedAppExtensions)}`;
      }
      const targetPath = await tempDir.path({
        prefix: fileName,
        suffix: '',
      });
      newApp = await downloadApp(newApp, targetPath);
    } else if (await fs.exists(newApp)) {
      // Use the local app
      logger.info(`Using local app '${newApp}'`);
      shouldUnzipApp = ZIP_EXTS.includes(path.extname(newApp));
    } else {
      let errorMessage = `The application at '${newApp}' does not exist or is not accessible`;
      // protocol value for 'C:\\temp' is 'c:', so we check the length as well
      if (_.isString(protocol) && protocol.length > 2) {
        errorMessage = `The protocol '${protocol}' used in '${newApp}' is not supported. ` +
          `Only http: and https: protocols are supported`;
      }
      throw new Error(errorMessage);
    }

    if (shouldUnzipApp) {
      const archivePath = newApp;
      archiveHash = await fs.hash(archivePath);
      if (APPLICATIONS_CACHE.has(app) && archiveHash === APPLICATIONS_CACHE.get(app).hash) {
        const {fullPath} = APPLICATIONS_CACHE.get(app);
        if (await fs.exists(fullPath)) {
          if (archivePath !== app) {
            await fs.rimraf(archivePath);
          }
          logger.info(`Will reuse previously cached application at '${fullPath}'`);
          return verifyAppExtension(fullPath, supportedAppExtensions);
        }
        logger.info(`The application at '${fullPath}' does not exist anymore. Deleting it from the cache`);
        APPLICATIONS_CACHE.del(app);
      }
      const tmpRoot = await tempDir.openDir();
      try {
        newApp = await unzipApp(archivePath, tmpRoot, supportedAppExtensions);
      } finally {
        if (newApp !== archivePath && archivePath !== app) {
          await fs.rimraf(archivePath);
        }
      }
      logger.info(`Unzipped local app to '${newApp}'`);
    } else if (!path.isAbsolute(newApp)) {
      newApp = path.resolve(process.cwd(), newApp);
      logger.warn(`The current application path '${app}' is relative, ` +
        `which might cause further issues. The path has been rewritten to '${newApp}' ` +
        `in order to avoid them`);
    }

    verifyAppExtension(newApp, supportedAppExtensions);

    if (app !== newApp && (archiveHash || currentModified)) {
      APPLICATIONS_CACHE.set(app, {
        hash: archiveHash,
        lastModified: currentModified,
        fullPath: newApp,
      });
    }
    return newApp;
  });
}

async function downloadApp (app, targetPath) {
  let appUrl;
  try {
    appUrl = url.parse(app);
  } catch (err) {
    throw new Error(`Invalid App URL (${app})`);
  }

  try {
    const started = process.hrtime();
    // don't use request-promise here, we need streams
    await new B((resolve, reject) => {
      request(appUrl.href)
        .on('error', reject) // handle real errors, like connection errors
        .on('response', (res) => {
          // handle responses that fail, like 404s
          if (res.statusCode >= 400) {
            return reject(`Error downloading file: ${res.statusCode}`);
          }
        })
        .pipe(_fs.createWriteStream(targetPath))
        .on('close', resolve);
    });
    const [seconds, ns] = process.hrtime(started);
    const secondsElapsed = seconds + ns / 1E09;
    const {size} = await fs.stat(targetPath);
    logger.debug(`'${appUrl.href}' (${util.toReadableSizeString(size)}) ` +
      `has been downloaded to '${targetPath}' in ${secondsElapsed.toFixed(3)}s`);
    if (secondsElapsed >= 2) {
      const bytesPerSec = Math.floor(size / secondsElapsed);
      logger.debug(`Approximate download speed: ${util.toReadableSizeString(bytesPerSec)}/s`);
    }
    return targetPath;
  } catch (err) {
    throw new Error(`Problem downloading app from url ${appUrl.href}: ${err.message}`);
  }
}

async function walkDir (dir) {
  const result = [];
  for (const name of await fs.readdir(dir)) {
    const currentPath = path.join(dir, name);
    result.push(currentPath);
    if ((await fs.stat(currentPath)).isDirectory()) {
      result.push(...(await walkDir(currentPath)));
    }
  }
  return result;
}

async function unzipApp (zipPath, dstRoot, supportedAppExtensions) {
  await zip.assertValidZip(zipPath);

  if (!_.isArray(supportedAppExtensions)) {
    supportedAppExtensions = [supportedAppExtensions];
  }

  const tmpRoot = await tempDir.openDir();
  try {
    logger.debug(`Unzipping '${zipPath}'`);
    await zip.extractAllTo(zipPath, tmpRoot);
    const allExtractedItems = await walkDir(tmpRoot);
    logger.debug(`Extracted ${allExtractedItems.length} item(s) from '${zipPath}'`);
    const isSupportedAppItem = (relativePath) => supportedAppExtensions.includes(path.extname(relativePath))
      || _.some(supportedAppExtensions, (x) => relativePath.includes(`${x}${path.sep}`));
    const itemsToKeep = allExtractedItems
      .map((itemPath) => path.relative(tmpRoot, itemPath))
      .filter((relativePath) => isSupportedAppItem(relativePath))
      .map((relativePath) => path.resolve(tmpRoot, relativePath));
    const itemsToRemove = _.difference(allExtractedItems, itemsToKeep)
      // Avoid parent folders to be recursively removed
      .filter((itemToRemovePath) => !_.some(itemsToKeep, (itemToKeepPath) => itemToKeepPath.startsWith(itemToRemovePath)));
    await B.all(itemsToRemove, async (itemPath) => {
      if (await fs.exists(itemPath)) {
        await fs.rimraf(itemPath);
      }
    });
    const allBundleItems = (await walkDir(tmpRoot))
      .map((itemPath) => path.relative(tmpRoot, itemPath))
      .filter((relativePath) => isSupportedAppItem(relativePath))
      // Get the top level match
      .sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
    if (_.isEmpty(allBundleItems)) {
      throw new Error(`App zip unzipped OK, but we could not find ${supportedAppExtensions} bundle(s) ` +
        `in it. Make sure your archive contains ${supportedAppExtensions} package(s) ` +
        `and nothing else`);
    }
    const matchedBundle = _.first(allBundleItems);
    logger.debug(`Matched ${allBundleItems.length} item(s) in the extracted archive. ` +
      `Assuming '${matchedBundle}' is the correct bundle`);
    await fs.mv(path.resolve(tmpRoot, matchedBundle), path.resolve(dstRoot, matchedBundle), {
      mkdirp: true
    });
    return path.resolve(dstRoot, matchedBundle);
  } finally {
    await fs.rimraf(tmpRoot);
  }
}

function isPackageOrBundle (app) {
  return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
}

function getCoordDefault (val) {
  // going the long way and checking for undefined and null since
  // we can't be assured `elId` is a string and not an int. Same
  // thing with destElement below.
  return util.hasValue(val) ? val : 0.5;
}

function getSwipeTouchDuration (waitGesture) {
  // the touch action api uses ms, we want seconds
  // 0.8 is the default time for the operation
  let duration = 0.8;
  if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) {
    duration = waitGesture.options.ms / 1000;
    if (duration === 0) {
      // set to a very low number, since they wanted it fast
      // but below 0.1 becomes 0 steps, which causes errors
      duration = 0.1;
    }
  }
  return duration;
}

/**
 * Recursively find all instances of the key 'inKey' and rename them 'outKey'
 * @param {*} input Any type of input
 * @param {String} inKey The key name to replace
 * @param {String} outKey The key name to replace it with
 */
function renameKey (input, inKey, outKey) {
  if (_.isArray(input)) {
    return input.map((item) => renameKey(item, inKey, outKey));
  } else if (_.isPlainObject(input)) {
    return _.reduce(input, (resultObj, value, key) => ({
      ...resultObj,
      [key === inKey ? outKey : key]: renameKey(value, inKey, outKey),
    }), {});
  }

  return input;
}

export {
  configureApp, isPackageOrBundle, getCoordDefault, getSwipeTouchDuration, renameKey,
};
<commit_msg>Make sure the app is not cleaned up if relative path is provided (#289)<commit_after>import _ from 'lodash';
import path from 'path';
import url from 'url';
import logger from './logger';
import _fs from 'fs';
import B from 'bluebird';
import { tempDir, fs, util, zip } from 'appium-support';
import request from 'request';
import asyncRequest from 'request-promise';
import LRU from 'lru-cache';
import AsyncLock from 'async-lock';
import sanitize from 'sanitize-filename';

const ZIP_EXTS = ['.zip', '.ipa'];
const ZIP_MIME_TYPES = [
  'application/zip',
  'application/x-zip-compressed',
  'multipart/x-zip',
];
const APPLICATIONS_CACHE = new LRU({
  max: 100,
});
const APPLICATIONS_CACHE_GUARD = new AsyncLock();
const SANITIZE_REPLACEMENT = '-';

async function retrieveHeaders (link) {
  try {
    const response = await asyncRequest({
      url: link,
      method: 'HEAD',
      resolveWithFullResponse: true,
      timeout: 5000,
    });
    return response.headers;
  } catch (e) {
    logger.debug(`Cannot send HEAD request to '${link}'. Original error: ${e.message}`);
  }
  return {};
}

function getCachedApplicationPath (link, currentModified) {
  if (!APPLICATIONS_CACHE.has(link) || !currentModified) {
    return null;
  }

  const {lastModified, fullPath} = APPLICATIONS_CACHE.get(link);
  if (lastModified && currentModified.getTime() <= lastModified.getTime()) {
    logger.debug(`Reusing already downloaded application at '${fullPath}'`);
    return fullPath;
  }
  logger.debug(`'Last-Modified' timestamp of '${link}' has been updated. ` +
    `An updated copy of the application is going to be downloaded.`);
  return null;
}

function verifyAppExtension (app, supportedAppExtensions) {
  if (supportedAppExtensions.includes(path.extname(app))) {
    return app;
  }
  throw new Error(`New app path '${app}' did not have extension(s) '${supportedAppExtensions}'`);
}

async function configureApp (app, supportedAppExtensions) {
  if (!_.isString(app)) {
    // immediately shortcircuit if not given an app
    return;
  }
  if (!_.isArray(supportedAppExtensions)) {
    supportedAppExtensions = [supportedAppExtensions];
  }

  let newApp = app;
  let shouldUnzipApp = false;
  let archiveHash = null;
  let currentModified = null;
  const {protocol} = url.parse(newApp);
  const isUrl = ['http:', 'https:'].includes(protocol);

  return await APPLICATIONS_CACHE_GUARD.acquire(app, async () => {
    if (isUrl) {
      // Use the app from remote URL
      logger.info(`Using downloadable app '${newApp}'`);
      const headers = await retrieveHeaders(newApp);
      if (headers['last-modified']) {
        logger.debug(`Last-Modified: ${headers['last-modified']}`);
        currentModified = new Date(headers['last-modified']);
      }
      const cachedPath = getCachedApplicationPath(app, currentModified);
      if (cachedPath) {
        if (await fs.exists(cachedPath)) {
          logger.info(`Reusing the previously downloaded application at '${cachedPath}'`);
          return verifyAppExtension(cachedPath, supportedAppExtensions);
        }
        logger.info(`The application at '${cachedPath}' does not exist anymore. Deleting it from the cache`);
        APPLICATIONS_CACHE.del(app);
      }

      let fileName = null;
      // to determine if we need to unzip the app, we have a number of places
      // to look: content type, content disposition, or the file extension
      if (ZIP_EXTS.includes(path.extname(newApp))) {
        fileName = sanitize(path.basename(newApp), {
          replacement: SANITIZE_REPLACEMENT
        });
        shouldUnzipApp = true;
      }
      if (headers['content-type']) {
        logger.debug(`Content-Type: ${headers['content-type']}`);
        // the filetype may not be obvious for certain urls, so check the mime type too
        if (ZIP_MIME_TYPES.includes(headers['content-type'])) {
          if (!fileName) {
            fileName = 'appium-app.zip';
          }
          shouldUnzipApp = true;
        }
      }
      if (headers['content-disposition'] && /^attachment/i.test(headers['content-disposition'])) {
        const match = /filename="([^"]+)/i.exec(headers['content-disposition']);
        if (match) {
          logger.debug(`Parsed file name '${match[1]}' from 'Content-Disposition' header`);
          fileName = sanitize(match[1], {
            replacement: SANITIZE_REPLACEMENT
          });
          shouldUnzipApp = shouldUnzipApp || ZIP_EXTS.includes(path.extname(fileName));
        }
      }
      if (!fileName) {
        // assign the default extension if none has been detected
        fileName = `appium-app${_.first(supportedAppExtensions)}`;
      }
      const targetPath = await tempDir.path({
        prefix: fileName,
        suffix: '',
      });
      newApp = await downloadApp(newApp, targetPath);
    } else if (await fs.exists(newApp)) {
      // Use the local app
      logger.info(`Using local app '${newApp}'`);
      shouldUnzipApp = ZIP_EXTS.includes(path.extname(newApp));
    } else {
      let errorMessage = `The application at '${newApp}' does not exist or is not accessible`;
      // protocol value for 'C:\\temp' is 'c:', so we check the length as well
      if (_.isString(protocol) && protocol.length > 2) {
        errorMessage = `The protocol '${protocol}' used in '${newApp}' is not supported. ` +
          `Only http: and https: protocols are supported`;
      }
      throw new Error(errorMessage);
    }

    if (shouldUnzipApp) {
      const archivePath = newApp;
      archiveHash = await fs.hash(archivePath);
      if (APPLICATIONS_CACHE.has(app) && archiveHash === APPLICATIONS_CACHE.get(app).hash) {
        const {fullPath} = APPLICATIONS_CACHE.get(app);
        if (await fs.exists(fullPath)) {
          if (archivePath !== app) {
            await fs.rimraf(archivePath);
          }
          logger.info(`Will reuse previously cached application at '${fullPath}'`);
          return verifyAppExtension(fullPath, supportedAppExtensions);
        }
        logger.info(`The application at '${fullPath}' does not exist anymore. Deleting it from the cache`);
        APPLICATIONS_CACHE.del(app);
      }
      const tmpRoot = await tempDir.openDir();
      try {
        newApp = await unzipApp(archivePath, tmpRoot, supportedAppExtensions);
      } finally {
        if (newApp !== archivePath && archivePath !== app) {
          await fs.rimraf(archivePath);
        }
      }
      logger.info(`Unzipped local app to '${newApp}'`);
    } else if (!path.isAbsolute(newApp)) {
      newApp = path.resolve(process.cwd(), newApp);
      logger.warn(`The current application path '${app}' is not absolute ` +
        `and has been rewritten to '${newApp}'. Consider using absolute paths rather than relative`);
      app = newApp;
    }

    verifyAppExtension(newApp, supportedAppExtensions);

    if (app !== newApp && (archiveHash || currentModified)) {
      APPLICATIONS_CACHE.set(app, {
        hash: archiveHash,
        lastModified: currentModified,
        fullPath: newApp,
      });
    }
    return newApp;
  });
}

async function downloadApp (app, targetPath) {
  let appUrl;
  try {
    appUrl = url.parse(app);
  } catch (err) {
    throw new Error(`Invalid App URL (${app})`);
  }

  try {
    const started = process.hrtime();
    // don't use request-promise here, we need streams
    await new B((resolve, reject) => {
      request(appUrl.href)
        .on('error', reject) // handle real errors, like connection errors
        .on('response', (res) => {
          // handle responses that fail, like 404s
          if (res.statusCode >= 400) {
            return reject(`Error downloading file: ${res.statusCode}`);
          }
        })
        .pipe(_fs.createWriteStream(targetPath))
        .on('close', resolve);
    });
    const [seconds, ns] = process.hrtime(started);
    const secondsElapsed = seconds + ns / 1E09;
    const {size} = await fs.stat(targetPath);
    logger.debug(`'${appUrl.href}' (${util.toReadableSizeString(size)}) ` +
      `has been downloaded to '${targetPath}' in ${secondsElapsed.toFixed(3)}s`);
    if (secondsElapsed >= 2) {
      const bytesPerSec = Math.floor(size / secondsElapsed);
      logger.debug(`Approximate download speed: ${util.toReadableSizeString(bytesPerSec)}/s`);
    }
    return targetPath;
  } catch (err) {
    throw new Error(`Problem downloading app from url ${appUrl.href}: ${err.message}`);
  }
}

async function walkDir (dir) {
  const result = [];
  for (const name of await fs.readdir(dir)) {
    const currentPath = path.join(dir, name);
    result.push(currentPath);
    if ((await fs.stat(currentPath)).isDirectory()) {
      result.push(...(await walkDir(currentPath)));
    }
  }
  return result;
}

async function unzipApp (zipPath, dstRoot, supportedAppExtensions) {
  await zip.assertValidZip(zipPath);

  if (!_.isArray(supportedAppExtensions)) {
    supportedAppExtensions = [supportedAppExtensions];
  }

  const tmpRoot = await tempDir.openDir();
  try {
    logger.debug(`Unzipping '${zipPath}'`);
    await zip.extractAllTo(zipPath, tmpRoot);
    const allExtractedItems = await walkDir(tmpRoot);
    logger.debug(`Extracted ${allExtractedItems.length} item(s) from '${zipPath}'`);
    const isSupportedAppItem = (relativePath) => supportedAppExtensions.includes(path.extname(relativePath))
      || _.some(supportedAppExtensions, (x) => relativePath.includes(`${x}${path.sep}`));
    const itemsToKeep = allExtractedItems
      .map((itemPath) => path.relative(tmpRoot, itemPath))
      .filter((relativePath) => isSupportedAppItem(relativePath))
      .map((relativePath) => path.resolve(tmpRoot, relativePath));
    const itemsToRemove = _.difference(allExtractedItems, itemsToKeep)
      // Avoid parent folders to be recursively removed
      .filter((itemToRemovePath) => !_.some(itemsToKeep, (itemToKeepPath) => itemToKeepPath.startsWith(itemToRemovePath)));
    await B.all(itemsToRemove, async (itemPath) => {
      if (await fs.exists(itemPath)) {
        await fs.rimraf(itemPath);
      }
    });
    const allBundleItems = (await walkDir(tmpRoot))
      .map((itemPath) => path.relative(tmpRoot, itemPath))
      .filter((relativePath) => isSupportedAppItem(relativePath))
      // Get the top level match
      .sort((a, b) => a.split(path.sep).length - b.split(path.sep).length);
    if (_.isEmpty(allBundleItems)) {
      throw new Error(`App zip unzipped OK, but we could not find ${supportedAppExtensions} bundle(s) ` +
        `in it. Make sure your archive contains ${supportedAppExtensions} package(s) ` +
        `and nothing else`);
    }
    const matchedBundle = _.first(allBundleItems);
    logger.debug(`Matched ${allBundleItems.length} item(s) in the extracted archive. ` +
      `Assuming '${matchedBundle}' is the correct bundle`);
    await fs.mv(path.resolve(tmpRoot, matchedBundle), path.resolve(dstRoot, matchedBundle), {
      mkdirp: true
    });
    return path.resolve(dstRoot, matchedBundle);
  } finally {
    await fs.rimraf(tmpRoot);
  }
}

function isPackageOrBundle (app) {
  return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
}

function getCoordDefault (val) {
  // going the long way and checking for undefined and null since
  // we can't be assured `elId` is a string and not an int. Same
  // thing with destElement below.
  return util.hasValue(val) ? val : 0.5;
}

function getSwipeTouchDuration (waitGesture) {
  // the touch action api uses ms, we want seconds
  // 0.8 is the default time for the operation
  let duration = 0.8;
  if (typeof waitGesture.options.ms !== 'undefined' && waitGesture.options.ms) {
    duration = waitGesture.options.ms / 1000;
    if (duration === 0) {
      // set to a very low number, since they wanted it fast
      // but below 0.1 becomes 0 steps, which causes errors
      duration = 0.1;
    }
  }
  return duration;
}

/**
 * Recursively find all instances of the key 'inKey' and rename them 'outKey'
 * @param {*} input Any type of input
 * @param {String} inKey The key name to replace
 * @param {String} outKey The key name to replace it with
 */
function renameKey (input, inKey, outKey) {
  if (_.isArray(input)) {
    return input.map((item) => renameKey(item, inKey, outKey));
  } else if (_.isPlainObject(input)) {
    return _.reduce(input, (resultObj, value, key) => ({
      ...resultObj,
      [key === inKey ? outKey : key]: renameKey(value, inKey, outKey),
    }), {});
  }

  return input;
}

export {
  configureApp, isPackageOrBundle, getCoordDefault, getSwipeTouchDuration, renameKey,
};
<|endoftext|>


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

  function parseLines(lines, options) {
    options = {
      caseInsensitive: true,
      section: {
        allowDuplicate: false
      },
      key: {
        allowDuplicate: false,
        replcaeDuplicate: false
      },
      value: {
        hasDelimiter: true,
        needToSort: true,
        delimiter: ' '
      }
    }

    function parseValue(value) {
      if (options.value.hasDelimiter) {
        let val = value.split(options.value.delimiter);
        if (options.value.needToSort) {
          val.sort(); // inplace sort
        }
        return val.join(options.value.delimiter);
      } else {
        return value;
      }
    }

    let state = null;
    let section = null;
    let data = {};

    // section, key, value, case_sensitive, comment
    for (let line of lines.split('\n')) {
      line = line.trim();

      if (options.caseInsensitive) {
        line = line.toLowerCase();
      }

      // ignore blank line
      if (line.length === 0) {
        continue;
      }

      if (line.match(/^\[([^\]])*\]$/)) { // ex: [sectionName]
        let sectionName = line.trim().substring(1, line.length - 1);
        let exist = data[sectionName];
        if (!options.section.allowDuplicate && exist) {
          throw new Error('Section duplicate not allowed');
        }
        data[sectionName] = data[sectionName] ? data[sectionName] : {};
        section = data[sectionName];
        continue;
      }

      if (!section) {
        throw new Error('There is no section');
      }

      // parse key value
      if (line.indexOf('=') < 0) {
        throw new Error('Invalid key value format. there is no character \'=\'')
      }

      let ary = line.split('=');
      let key = ary[0].trim();
      let value = ary[1].trim();

      if (!options.key.allowDuplicate && section[key]) {
        throw new Error('Key duplicate not allowed');
      }

      // section 안의 key 가 이미 존재하는 경우에는 reaplce 할지 무시할지를 결정한다.
      if (section[key]) {
        if (options.key.replcaeDuplicate) {
          section[key] = parseValue(value, options);
        }
      } else {
        section[key] = parseValue(value);
      }
    }
    return data;
  };

  module.exports = function(lines, options) {
    return parseLines(lines, options);
  }
})();
<commit_msg>improve: add bracket in section<commit_after>(function()  {
  "use strict";

  function parseLines(lines, options) {
    options = {
      caseInsensitive: true,
      section: {
        allowDuplicate: false
      },
      key: {
        allowDuplicate: false,
        replcaeDuplicate: false
      },
      value: {
        hasDelimiter: true,
        needToSort: true,
        delimiter: ' '
      }
    }

    function parseValue(value) {
      if (options.value.hasDelimiter) {
        let val = value.split(options.value.delimiter);
        if (options.value.needToSort) {
          val.sort(); // inplace sort
        }
        return val.join(options.value.delimiter);
      } else {
        return value;
      }
    }

    let state = null;
    let section = null;
    let data = {};

    // section, key, value, case_sensitive, comment
    for (let line of lines.split('\n')) {
      line = line.trim();

      if (options.caseInsensitive) {
        line = line.toLowerCase();
      }

      // ignore blank line
      if (line.length === 0) {
        continue;
      }

      if (line.match(/^\[([^\]])*\]$/)) { // ex: [sectionName]
        let sectionName = line.trim();
        let exist = data[sectionName];
        if (!options.section.allowDuplicate && exist) {
          throw new Error('Section duplicate not allowed');
        }
        data[sectionName] = data[sectionName] ? data[sectionName] : {};
        section = data[sectionName];
        continue;
      }

      if (!section) {
        throw new Error('There is no section');
      }

      // parse key value
      if (line.indexOf('=') < 0) {
        throw new Error('Invalid key value format. there is no character \'=\'')
      }

      let ary = line.split('=');
      let key = ary[0].trim();
      let value = ary[1].trim();

      if (!options.key.allowDuplicate && section[key]) {
        throw new Error('Key duplicate not allowed');
      }

      // section 안의 key 가 이미 존재하는 경우에는 reaplce 할지 무시할지를 결정한다.
      if (section[key]) {
        if (options.key.replcaeDuplicate) {
          section[key] = parseValue(value, options);
        }
      } else {
        section[key] = parseValue(value);
      }
    }
    return data;
  };

  module.exports = function(lines, options) {
    return parseLines(lines, options);
  }
})();
<|endoftext|>


<commit_before>// Most of these tokens map to a function for encoding/decoding.
// This is subset of allowed tokens in the Erlang external term format.
// Yup. Magic numbers.
const BERT_TOKENS = {
  131: "start",
  70:  "new_float",
  77:  "bitstring",
  97:  "small_int",
  98:  "int",
  99:  "float",
  100: "atom",
  102: "port",
  103: "pid",
  104: "small_tuple",
  105: "tuple",
  106: "nil",
  107: "string",
  108: "list",
  109: "binary",
  110: "small_big",
  111: "big",
  115: "small_atom",
  116: "map",
  118: "atom_utf8",
  119: "small_atom_utf8",
}

/**
 * Represents a Bert object. That is to say, it is the JavaScript representation
 * of an Erlang native type.
 *
 * Although the encoder could perform "clever" conversions, like a regular
 * JavaScript array to an Erlang list type, it doesn't. The reason being that
 * an array could be a tuple or a list, or even an Erlang string! And strings
 * can be strings or binaries or atoms. Don't even get me started on numbers.
 * Forcing explicit typing like this causes less errors during encoding.
 */
class BertObj {
  constructor(type, value) {
    this.type = type
    this.value = value
  }
}

/**
 * The "BertClass" object is used to create a Bert object that can encode and
 * decode data using Erlang's external term format. It is a binary format, and
 * so the decoder expects inputs to be either strings, with each character being
 * one byte of data, or an array, with each element being one byte of data. The
 * encoding process will generate an array of byte data given a BertObj object.
 */
class Bert {
  static Nil() { return new BertObj("nil", []) }
  static Int(value) { return new BertObj("int", value) }
  static Float(value) { return new BertObj("new_float", value) }
  static Atom(value) { return new BertObj("atom", value) }
  static String(value) { return new BertObj("string", value) }
  static Binary(value) { return new BertObj("binary", value) }
  static Tuple(value) {
    if (!(value instanceof Array))
      throw "Must use an array to create tuples."
    return new BertObj("tuple", value)
  }
  static Map(value) {
    if (!(value instanceof Object || value instanceof Array))
      throw "Must use an object or an array to create maps."
    return new BertObj("map", value)
  }
  static List(value) {
    if (!(value instanceof Array))
      throw "Must use an array to create lists."
    return new BertObj("list", value)
  }
  static IntList(value) {
    if (!(value instanceof Array))
      throw "Must use an array to create lists."
    return new BertObj("string", value)
  }

  static four_byte_max_number() { return 134217727 }

  // Get the next "token" based on the data type.
  static next_token(data) {
    if (typeof(data) == "string")
      return [data.charCodeAt(0), data.substring(1)]
    else
      return [data[0], data.slice(1)]
  }

  // Splits the data in two, where the first part of the split is `length`
  // long and the second part are all remaining elements after that.
  static split(data, length) {
    if (length == null) { length = 1 }
    if (typeof(data) == "string")
      return [data.substring(0, length), data.substring(length)]
    else
      return [data.slice(0, length), data.slice(length)]
  }

  // Decode a string or array into a JavaScript object.
  static decode(data) {
    var parts = this.next_token(data)
    if (BERT_TOKENS[parts[0]] != "start")
      throw "Data must begin with an appropriate start token, actually starts with " + parts[0]
    var result = this.decode_data(parts[1])
    return result[0].value
  }

  static decode_data(data) {
    var parts  = this.next_token(data),
        token  = parts[0],
        data   = parts[1],
        type   = BERT_TOKENS[token],
        result = this["decode_" + type](data)
    return this.handle_result(type, result)
  }

  static handle_result(type, result) {
    return [new BertObj(type, result[0]), result[1]]
  }

  static decode_small_int(data) {
    return this.decode_int(data, 1)
  }

  static decode_int(data, byteLength) {
    if (byteLength == null) { byteLength = 4 }
    var parts  = this.split(data, byteLength),
        size   = 0,
        offset = 0
    for (var i = 0; i < byteLength; i++) {
      offset = (byteLength - i - 1) * 8
      size += data[i] << offset
    }
    return [size, parts[1]]
  }

  static decode_float(data) {
    var float = this.decode_string(data, 31)
    return [parseFloat(float[0]), float[1]]
  }

  static decode_atom(data) {
    return this.decode_string(data, 2)
  }

  static decode_port(data) {
    var atom     = this.decode_data(data),
        id       = this.decode_int(atom[1], 4),
        creation = this.decode_int(id[1], 1)
    return [atom[0] + "<" + id[0] + ">", creation[1]]
  }

  static decode_pid(data) {
    var atom     = this.decode_data(data),
        id       = this.decode_int(atom[1], 4),
        serial   = this.decode_int(id[1], 4),
        creation = this.decode_int(serial[1], 1)
    return [atom[0] + "<" + id[0] + ">", creation[1]]
  }

  static decode_small_tuple(data) {
    return this.decode_tuple(data, 1)
  }

  static decode_tuple(data, byteLength) {
    if (byteLength == null) { byteLength = 4 }
    var parts    = this.decode_int(data, byteLength),
        arity    = parts[0],
        elements = parts[1],
        tuple    = []
    for (var i = 0; i < arity; i++) {
      parts = this.decode_data(elements)
      tuple.push(parts[0].value)
      elements = parts[1]
    }
    return [tuple, elements]
  }

  static decode_nil(data) {
    return [[], data]
  }

  static decode_string(data, byteLength) {
    if (byteLength == null) { byteLength = 2 }
    var parts  = this.decode_int(data, byteLength),
        length = parts[0],
        atom   = ""
    parts = this.split(parts[1], length)
    for (var i = 0; i < length; i++) {
      atom += String.fromCharCode(parts[0][i])
    }
    return [atom, parts[1]]
  }

  static decode_list(data) {
    var parts    = this.decode_int(data, 4),
        length   = parts[0],
        elements = parts[1],
        arr      = []
    for (var i = 0; i < length; i++) {
      parts = this.decode_data(elements)
      arr.push(parts[0].value)
      elements = parts[1]
    }
    var removenil = this.decode_data(elements)
    if (removenil[0].type != "nil")
      throw "Lists parsed by this tool are expected to end properly, with a nil terminator."
    return [arr, removenil[1]]
  }

  static decode_binary(data) {
    return this.decode_string(data, 4)
  }

  static decode_small_big(data) {
    return this.decode_big(data, 1)
  }

  static decode_big(data, byteLength) {
    if (byteLength == null) { byteLength = 4 }
    var arity    = this.decode_int(data, byteLength),
        length   = arity[0],
        sign     = this.decode_int(arity[1], 1),
        parts    = this.split(sign[1], length),
        elements = parts[0],
        num      = 0
    for (var i = 0; i < length; i++) {
      num += elements[i] * Math.pow(256, i)
    }
    if (sign[0] != 0) { num *= -1 }
    return [num, parts[1]]
  }

  static decode_small_atom(data) {
    return this.decode_string(data, 1)
  }

  static decode_map(data) {
    var parts = this.decode_int(data, 4),
        arity = parts[0],
        pairs = parts[1],
        map = {},
        result, key, value
    for (var i = 0; i < arity; i++) {
      result = this.decode_data(pairs)
      key = result[0].value
      result = this.decode_data(result[1])
      value = result[0].value
      map[key] = value
      pairs = result[1]
    }
    return [map, pairs]
  }

  static decode_atom_utf8(data) {
    return this.decode_atom(data)
  }

  static decode_small_atom_utf8(data) {
    return this.decode_small_atom(data)
  }

  // Encode an object into an array of byte data.
  static encode(obj) {
    var buffer = [131]
    return this.encode_data(obj, buffer)
  }

  static smart_cast(obj) {
    if (obj == null)
      return this.Nil()

    var type = typeof(obj)
    if (type == 'number') {
      if (obj % 1 === 0)
        return this.Int(obj)
      else
        return this.Float(obj)
    }
    else if (type == 'string')
      return this.Binary(obj)
    else if (type == 'array') {
      if (obj.length == 0)
        return this.Nil()
      return this.List(obj)
    }
    else if (type == 'object')
      return this.Map(obj)
    else
      throw "Invalid object type: " + type + ". Cannot encode."
  }

  static encode_data(obj, buffer) {
    if (!(obj instanceof BertObj))
      obj = this.smart_cast(obj)
    return this["encode_" + obj.type](obj.value, buffer)
  }

  static encode_num(num, byteLength, buffer) {
    var value = 0
    for (var offset = (byteLength - 1) * 8; offset >= 0; offset -= 8) {
      value = num >> offset
      buffer.push(value)
      num = num - (value << offset)
    }
    return buffer
  }

  static encode_small_int(data, buffer) {
    if (data > 255)
      return this.encode_int(data, buffer)
    buffer.push(97)
    return this.encode_num(data, 1, buffer)
  }

  static encode_int(data, buffer) {
    if (data > this.four_byte_max_number())
      this.max_number_error()
    else if (data < 256)
      return this.encode_small_int(data, buffer)
    else
      buffer.push(98)
      return this.encode_num(data, 4, buffer)
  }

  static encode_nil(data, buffer) {
    buffer.push(106)
    return buffer
  }

  static encode_atom(data, buffer) {
    var length = data.length
    if (length >= 256)
      throw "Atoms may only be up to 255 bytes."
    buffer.push(100)
    buffer.push(0)
    buffer.push(length)
    this.encode_string_to_buffer(data, buffer)
    return buffer
  }

  static encode_string(data, buffer) {
    var length = data.length, bytes = 2
    if (length > 65535)
      return this.encode_list(data, buffer)

    buffer.push(107)
    this.encode_num(length, bytes, buffer)
    for (var i = 0; i < length; i++) {
      buffer.push(data.charCodeAt(i))
    }
    return buffer
  }

  static encode_list(data, buffer) {
    var length = data.length, bytes = 4
    if (length > this.four_byte_max_number())
      this.max_number_error()

    buffer.push(108)
    this.encode_num(length, bytes, buffer)
    for (var i = 0; i < length; i++) {
      this.encode_data(data[i], buffer)
    }
    this.encode_nil(this.Nil(), buffer)
    return buffer
  }

  static encode_tuple(data, buffer) {
    var length = data.length, bytes = 0
    if (length > this.four_byte_max_number())
      this.max_number_error()
    else if (length < 256) {
      buffer.push(104)
      bytes = 1
    }
    else {
      buffer.push(105)
      bytes = 4
    }
    this.encode_num(length, bytes, buffer)
    for (var i = 0; i < length; i++) {
      this.encode_data(data[i], buffer)
    }
    return buffer
  }

  static encode_map(data, buffer) {
    buffer.push(116)

    if (data instanceof Array)
      return this.encode_map_from_array(data, buffer)
    else
      return this.encode_map_from_object(data, buffer)
  }

  static encode_map_from_array(data, buffer) {
    let length = data.length,
        pair, key, value
    if (length > this.four_byte_max_number())
      this.max_number_error()

    this.encode_num(length, 4, buffer)
    for (var i = 0; i < length; i++) {
      pair = data[i]
      key = this.encode_data(pair[0], buffer)
      value = this.encode_data(pair[1], buffer)
    }
    return buffer
  }

  static encode_map_from_object(data, buffer) {
    let keys   = Object.keys(data),
        length = keys.length
    if (length > this.four_byte_max_number())
      this.max_number_error()

    this.encode_num(length, 4, buffer)
    for (var i = 0; i < length; i++) {
      let key = keys[i]
      this.encode_binary(key, buffer)
      this.encode_data(data[key], buffer)
    }
    return buffer
  }

  static encode_binary(data, buffer) {
    if (data.length > this.four_byte_max_number())
      this.max_number_error()

    buffer.push(109)
    this.encode_num(data.length, 4, buffer)
    return this.encode_string_to_buffer(data, buffer)
  }

  static encode_string_to_buffer(str, buffer) {
    for (var i = 0; i < str.length; i++) {
      buffer.push(str.charCodeAt(i))
    }
    return buffer
  }

  static max_number_error() {
    throw "Your data is too long. Seriously, way too long."
  }
}

export default Bert
<commit_msg>Cleanup map encoding<commit_after>// Most of these tokens map to a function for encoding/decoding.
// This is subset of allowed tokens in the Erlang external term format.
// Yup. Magic numbers.
const BERT_TOKENS = {
  131: "start",
  70:  "new_float",
  77:  "bitstring",
  97:  "small_int",
  98:  "int",
  99:  "float",
  100: "atom",
  102: "port",
  103: "pid",
  104: "small_tuple",
  105: "tuple",
  106: "nil",
  107: "string",
  108: "list",
  109: "binary",
  110: "small_big",
  111: "big",
  115: "small_atom",
  116: "map",
  118: "atom_utf8",
  119: "small_atom_utf8",
}

/**
 * Represents a Bert object. That is to say, it is the JavaScript representation
 * of an Erlang native type.
 *
 * Although the encoder could perform "clever" conversions, like a regular
 * JavaScript array to an Erlang list type, it doesn't. The reason being that
 * an array could be a tuple or a list, or even an Erlang string! And strings
 * can be strings or binaries or atoms. Don't even get me started on numbers.
 * Forcing explicit typing like this causes less errors during encoding.
 */
class BertObj {
  constructor(type, value) {
    this.type = type
    this.value = value
  }
}

/**
 * The "BertClass" object is used to create a Bert object that can encode and
 * decode data using Erlang's external term format. It is a binary format, and
 * so the decoder expects inputs to be either strings, with each character being
 * one byte of data, or an array, with each element being one byte of data. The
 * encoding process will generate an array of byte data given a BertObj object.
 */
class Bert {
  static Nil() { return new BertObj("nil", []) }
  static Int(value) { return new BertObj("int", value) }
  static Float(value) { return new BertObj("new_float", value) }
  static Atom(value) { return new BertObj("atom", value) }
  static String(value) { return new BertObj("string", value) }
  static Binary(value) { return new BertObj("binary", value) }
  static Tuple(value) {
    if (!(value instanceof Array))
      throw "Must use an array to create tuples."
    return new BertObj("tuple", value)
  }
  static Map(value) {
    if (!(value instanceof Object || value instanceof Array))
      throw "Must use an object or an array to create maps."
    return new BertObj("map", value)
  }
  static List(value) {
    if (!(value instanceof Array))
      throw "Must use an array to create lists."
    return new BertObj("list", value)
  }
  static IntList(value) {
    if (!(value instanceof Array))
      throw "Must use an array to create lists."
    return new BertObj("string", value)
  }

  static four_byte_max_number() { return 134217727 }

  // Get the next "token" based on the data type.
  static next_token(data) {
    if (typeof(data) == "string")
      return [data.charCodeAt(0), data.substring(1)]
    else
      return [data[0], data.slice(1)]
  }

  // Splits the data in two, where the first part of the split is `length`
  // long and the second part are all remaining elements after that.
  static split(data, length) {
    if (length == null) { length = 1 }
    if (typeof(data) == "string")
      return [data.substring(0, length), data.substring(length)]
    else
      return [data.slice(0, length), data.slice(length)]
  }

  // Decode a string or array into a JavaScript object.
  static decode(data) {
    var parts = this.next_token(data)
    if (BERT_TOKENS[parts[0]] != "start")
      throw "Data must begin with an appropriate start token, actually starts with " + parts[0]
    var result = this.decode_data(parts[1])
    return result[0].value
  }

  static decode_data(data) {
    var parts  = this.next_token(data),
        token  = parts[0],
        data   = parts[1],
        type   = BERT_TOKENS[token],
        result = this["decode_" + type](data)
    return this.handle_result(type, result)
  }

  static handle_result(type, result) {
    return [new BertObj(type, result[0]), result[1]]
  }

  static decode_small_int(data) {
    return this.decode_int(data, 1)
  }

  static decode_int(data, byteLength) {
    if (byteLength == null) { byteLength = 4 }
    var parts  = this.split(data, byteLength),
        size   = 0,
        offset = 0
    for (var i = 0; i < byteLength; i++) {
      offset = (byteLength - i - 1) * 8
      size += data[i] << offset
    }
    return [size, parts[1]]
  }

  static decode_float(data) {
    var float = this.decode_string(data, 31)
    return [parseFloat(float[0]), float[1]]
  }

  static decode_atom(data) {
    return this.decode_string(data, 2)
  }

  static decode_port(data) {
    var atom     = this.decode_data(data),
        id       = this.decode_int(atom[1], 4),
        creation = this.decode_int(id[1], 1)
    return [atom[0] + "<" + id[0] + ">", creation[1]]
  }

  static decode_pid(data) {
    var atom     = this.decode_data(data),
        id       = this.decode_int(atom[1], 4),
        serial   = this.decode_int(id[1], 4),
        creation = this.decode_int(serial[1], 1)
    return [atom[0] + "<" + id[0] + ">", creation[1]]
  }

  static decode_small_tuple(data) {
    return this.decode_tuple(data, 1)
  }

  static decode_tuple(data, byteLength) {
    if (byteLength == null) { byteLength = 4 }
    var parts    = this.decode_int(data, byteLength),
        arity    = parts[0],
        elements = parts[1],
        tuple    = []
    for (var i = 0; i < arity; i++) {
      parts = this.decode_data(elements)
      tuple.push(parts[0].value)
      elements = parts[1]
    }
    return [tuple, elements]
  }

  static decode_nil(data) {
    return [[], data]
  }

  static decode_string(data, byteLength) {
    if (byteLength == null) { byteLength = 2 }
    var parts  = this.decode_int(data, byteLength),
        length = parts[0],
        atom   = ""
    parts = this.split(parts[1], length)
    for (var i = 0; i < length; i++) {
      atom += String.fromCharCode(parts[0][i])
    }
    return [atom, parts[1]]
  }

  static decode_list(data) {
    var parts    = this.decode_int(data, 4),
        length   = parts[0],
        elements = parts[1],
        arr      = []
    for (var i = 0; i < length; i++) {
      parts = this.decode_data(elements)
      arr.push(parts[0].value)
      elements = parts[1]
    }
    var removenil = this.decode_data(elements)
    if (removenil[0].type != "nil")
      throw "Lists parsed by this tool are expected to end properly, with a nil terminator."
    return [arr, removenil[1]]
  }

  static decode_binary(data) {
    return this.decode_string(data, 4)
  }

  static decode_small_big(data) {
    return this.decode_big(data, 1)
  }

  static decode_big(data, byteLength) {
    if (byteLength == null) { byteLength = 4 }
    var arity    = this.decode_int(data, byteLength),
        length   = arity[0],
        sign     = this.decode_int(arity[1], 1),
        parts    = this.split(sign[1], length),
        elements = parts[0],
        num      = 0
    for (var i = 0; i < length; i++) {
      num += elements[i] * Math.pow(256, i)
    }
    if (sign[0] != 0) { num *= -1 }
    return [num, parts[1]]
  }

  static decode_small_atom(data) {
    return this.decode_string(data, 1)
  }

  static decode_map(data) {
    var parts = this.decode_int(data, 4),
        arity = parts[0],
        pairs = parts[1],
        map = {},
        result, key, value
    for (var i = 0; i < arity; i++) {
      result = this.decode_data(pairs)
      key = result[0].value
      result = this.decode_data(result[1])
      value = result[0].value
      map[key] = value
      pairs = result[1]
    }
    return [map, pairs]
  }

  static decode_atom_utf8(data) {
    return this.decode_atom(data)
  }

  static decode_small_atom_utf8(data) {
    return this.decode_small_atom(data)
  }

  // Encode an object into an array of byte data.
  static encode(obj) {
    var buffer = [131]
    return this.encode_data(obj, buffer)
  }

  static smart_cast(obj) {
    if (obj == null)
      return this.Nil()

    var type = typeof(obj)
    if (type == 'number') {
      if (obj % 1 === 0)
        return this.Int(obj)
      else
        return this.Float(obj)
    }
    else if (type == 'string')
      return this.Binary(obj)
    else if (type == 'array') {
      if (obj.length == 0)
        return this.Nil()
      return this.List(obj)
    }
    else if (type == 'object')
      return this.Map(obj)
    else
      throw "Invalid object type: " + type + ". Cannot encode."
  }

  static encode_data(obj, buffer) {
    if (!(obj instanceof BertObj))
      obj = this.smart_cast(obj)
    return this["encode_" + obj.type](obj.value, buffer)
  }

  static encode_num(num, byteLength, buffer) {
    var value = 0
    for (var offset = (byteLength - 1) * 8; offset >= 0; offset -= 8) {
      value = num >> offset
      buffer.push(value)
      num = num - (value << offset)
    }
    return buffer
  }

  static encode_small_int(data, buffer) {
    if (data > 255)
      return this.encode_int(data, buffer)
    buffer.push(97)
    return this.encode_num(data, 1, buffer)
  }

  static encode_int(data, buffer) {
    if (data > this.four_byte_max_number())
      this.max_number_error()
    else if (data < 256)
      return this.encode_small_int(data, buffer)
    else
      buffer.push(98)
      return this.encode_num(data, 4, buffer)
  }

  static encode_nil(data, buffer) {
    buffer.push(106)
    return buffer
  }

  static encode_atom(data, buffer) {
    var length = data.length
    if (length >= 256)
      throw "Atoms may only be up to 255 bytes."
    buffer.push(100)
    buffer.push(0)
    buffer.push(length)
    this.encode_string_to_buffer(data, buffer)
    return buffer
  }

  static encode_string(data, buffer) {
    var length = data.length, bytes = 2
    if (length > 65535)
      return this.encode_list(data, buffer)

    buffer.push(107)
    this.encode_num(length, bytes, buffer)
    for (var i = 0; i < length; i++) {
      buffer.push(data.charCodeAt(i))
    }
    return buffer
  }

  static encode_list(data, buffer) {
    var length = data.length, bytes = 4
    if (length > this.four_byte_max_number())
      this.max_number_error()

    buffer.push(108)
    this.encode_num(length, bytes, buffer)
    for (var i = 0; i < length; i++) {
      this.encode_data(data[i], buffer)
    }
    this.encode_nil(this.Nil(), buffer)
    return buffer
  }

  static encode_tuple(data, buffer) {
    var length = data.length, bytes = 0
    if (length > this.four_byte_max_number())
      this.max_number_error()
    else if (length < 256) {
      buffer.push(104)
      bytes = 1
    }
    else {
      buffer.push(105)
      bytes = 4
    }
    this.encode_num(length, bytes, buffer)
    for (var i = 0; i < length; i++) {
      this.encode_data(data[i], buffer)
    }
    return buffer
  }

  static encode_map(data, buffer) {
    buffer.push(116)

    if (data instanceof Array)
      return this.encode_map_from_array(data, buffer)
    else
      return this.encode_map_from_object(data, buffer)
  }

  static encode_map_from_array(data, buffer) {
    let length = data.length
    if (length > this.four_byte_max_number())
      this.max_number_error()

    this.encode_num(length, 4, buffer)
    for (var i = 0; i < length; i++) {
      let pair = data[i]
      this.encode_data(pair[0], buffer)
      this.encode_data(pair[1], buffer)
    }
    return buffer
  }

  static encode_map_from_object(data, buffer) {
    let keys   = Object.keys(data),
        length = keys.length
    if (length > this.four_byte_max_number())
      this.max_number_error()

    this.encode_num(length, 4, buffer)
    for (var i = 0; i < length; i++) {
      let key = keys[i]
      this.encode_binary(key, buffer)
      this.encode_data(data[key], buffer)
    }
    return buffer
  }

  static encode_binary(data, buffer) {
    if (data.length > this.four_byte_max_number())
      this.max_number_error()

    buffer.push(109)
    this.encode_num(data.length, 4, buffer)
    return this.encode_string_to_buffer(data, buffer)
  }

  static encode_string_to_buffer(str, buffer) {
    for (var i = 0; i < str.length; i++) {
      buffer.push(str.charCodeAt(i))
    }
    return buffer
  }

  static max_number_error() {
    throw "Your data is too long. Seriously, way too long."
  }
}

export default Bert
<|endoftext|>


<commit_before>import React from 'react';
import Types from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link, Redirect } from 'react-router-dom';
import { Message } from 'retranslate';

import { reduxForm } from 'redux-form';
import ThirdPillarTermsAgreement from './ThirdPillarTermsAgreement';
import PoliticallyExposedPersonAgreement from '../../../aml/PoliticallyExposedPersonAgreement';
import { actions as exchangeActions } from '../../../exchange';
import FundTransferTable from '../../secondPillar/confirmMandate/fundTransferTable';
import ResidencyAgreement from '../../../aml/ResidencyAgreement';
import { AuthenticationLoader, ErrorMessage, Loader } from '../../../common';
import { hasAddress as isAddressFilled } from '../../../common/user/address';
import OccupationAgreement from '../../../aml/OccupationAgreement';
import { hasContactDetailsAmlCheck as isContactDetailsAmlCheckPassed } from '../../../aml';

export const ConfirmThirdPillarMandate = ({
  previousPath,
  nextPath,
  signedMandateId,
  exchangeExistingUnits,
  exchangeableSourceFunds,
  selectedFutureContributionsFund,
  agreedToTerms,
  isResident,
  isPoliticallyExposed,
  occupation,
  loadingSourceFunds,
  address,
  hasAddress,
  hasContactDetailsAmlCheck,
  isUserConverted,
  onSign,
  onPreview,
  onCancelSigningMandate,
  onCloseErrorMessages,
  loadingMandate,
  mandateSigningControlCode,
  mandateSigningError,
}) => {
  const buttonDisabled = !agreedToTerms || !isResident || isPoliticallyExposed || !occupation;
  return (
    <>
      {isUserConverted && <Redirect to={nextPath} />}
      {signedMandateId && <Redirect to={nextPath} />}
      {(!hasAddress || !hasContactDetailsAmlCheck) && <Redirect to={previousPath} />}
      {loadingMandate || mandateSigningControlCode ? (
        <AuthenticationLoader
          controlCode={mandateSigningControlCode}
          onCancel={onCancelSigningMandate}
          overlayed
        />
      ) : (
        ''
      )}

      <Message>confirmThirdPillarMandate.intro</Message>

      {selectedFutureContributionsFund && (

          <Message>confirmThirdPillarMandate.contribution</Message>

            {selectedFutureContributionsFund.name}

      )}
      {loadingSourceFunds && <Loader className="align-middle" />}
      {exchangeExistingUnits &&
        !loadingSourceFunds &&
        exchangeableSourceFunds &&
        !!exchangeableSourceFunds.length &&
        selectedFutureContributionsFund && (

            <Message>confirmThirdPillarMandate.exchangeExistingUnits</Message>

              <FundTransferTable
                selections={createSelectionsFromFundsToFund(
                  exchangeableSourceFunds,
                  selectedFutureContributionsFund,
                )}
              />

        )}

      <ThirdPillarTermsAgreement />

      <PoliticallyExposedPersonAgreement className="mt-3" />
      <ResidencyAgreement className="mt-3" />
      <OccupationAgreement className="col-md-5 mt-3 px-0" />

      {mandateSigningError ? (
        <ErrorMessage errors={mandateSigningError.body} onCancel={onCloseErrorMessages} overlayed />
      ) : (
        ''
      )}

        <button
          type="button"
          className="btn btn-primary mb-2 mr-2"
          disabled={buttonDisabled}
          onClick={() => {
            onSign(
              getMandate(
                exchangeExistingUnits,
                exchangeableSourceFunds,
                selectedFutureContributionsFund,
                address,
              ),
              { isResident, isPoliticallyExposed, occupation },
            );
          }}
        >
          <Message>confirmThirdPillarMandate.sign</Message>
        </button>

        <button
          type="button"
          className="btn btn-secondary mb-2 mr-2"
          disabled={buttonDisabled}
          onClick={() => {
            onPreview(
              getMandate(
                exchangeExistingUnits,
                exchangeableSourceFunds,
                selectedFutureContributionsFund,
                address,
              ),
              { isResident, isPoliticallyExposed, occupation },
            );
          }}
        >
          <Message>confirmThirdPillarMandate.preview</Message>
        </button>

        <Link to={previousPath}>
          <button type="button" className="btn btn-secondary mb-2">
            <Message>confirmThirdPillarMandate.back</Message>
          </button>
        </Link>

    </>
  );
};

function getMandate(exchangeExistingUnits, sourceFunds, targetFund, address) {
  return {
    fundTransferExchanges: exchangeExistingUnits
      ? sourceFunds.map(sourceFund => ({
          amount: 1,
          sourceFundIsin: sourceFund.isin,
          targetFundIsin: targetFund.isin,
        }))
      : [],
    futureContributionFundIsin: targetFund.isin,
    address,
  };
}

function createSelectionsFromFundsToFund(sourceFunds, targetFund) {
  return sourceFunds.map(sourceFund => ({
    sourceFundIsin: sourceFund.isin,
    sourceFundName: sourceFund.name,
    targetFundIsin: targetFund.isin,
    targetFundName: targetFund.name,
    percentage: 1,
  }));
}

const fundType = Types.shape({ isin: Types.string, name: Types.string });

ConfirmThirdPillarMandate.propTypes = {
  previousPath: Types.string,
  nextPath: Types.string,

  loadingMandate: Types.bool,
  mandateSigningControlCode: Types.string,
  mandateSigningError: Types.string,
  signedMandateId: Types.number,
  exchangeExistingUnits: Types.bool,
  exchangeableSourceFunds: Types.arrayOf(fundType),
  selectedFutureContributionsFund: fundType,
  agreedToTerms: Types.bool,
  isResident: Types.bool,
  isPoliticallyExposed: Types.bool,
  occupation: Types.string,
  loadingSourceFunds: Types.bool,
  address: Types.shape({}),
  hasAddress: Types.bool,
  hasContactDetailsAmlCheck: Types.bool,
  isUserConverted: Types.bool,
  onSign: Types.func,
  onPreview: Types.func,
  onCancelSigningMandate: Types.func,
  onCloseErrorMessages: Types.func,
};

ConfirmThirdPillarMandate.defaultProps = {
  previousPath: '',
  nextPath: '',

  loadingMandate: false,
  mandateSigningControlCode: null,
  mandateSigningError: null,
  signedMandateId: null,
  exchangeExistingUnits: null,
  exchangeableSourceFunds: null,
  selectedFutureContributionsFund: {},
  agreedToTerms: false,
  isResident: null,
  isPoliticallyExposed: null,
  occupation: null,
  loadingSourceFunds: false,
  hasAddress: false,
  hasContactDetailsAmlCheck: false,
  address: {},
  isUserConverted: false,
  onSign: () => {},
  onPreview: () => {},
  onCancelSigningMandate: () => {},
  onCloseErrorMessages: () => {},
};

const mapStateToProps = state => ({
  loadingMandate: state.exchange.loadingMandate,
  mandateSigningControlCode: state.exchange.mandateSigningControlCode,
  mandateSigningError: state.exchange.mandateSigningError,
  signedMandateId: state.thirdPillar.signedMandateId,
  selectedFutureContributionsFund: state.thirdPillar.targetFunds.find(
    fund => fund.isin === state.thirdPillar.selectedFutureContributionsFundIsin,
  ),
  agreedToTerms: state.thirdPillar.agreedToTerms,
  isResident: state.aml.isResident,
  isPoliticallyExposed: state.aml.isPoliticallyExposed,
  occupation: state.aml.occupation,
  exchangeableSourceFunds: state.thirdPillar.exchangeableSourceFunds,
  exchangeExistingUnits: state.thirdPillar.exchangeExistingUnits,
  loadingSourceFunds: state.thirdPillar.loadingSourceFunds,
  address: (state.login.user || {}).address,
  hasAddress: !state.login.user || isAddressFilled(state.login.user),
  hasContactDetailsAmlCheck: isContactDetailsAmlCheckPassed(state.aml.missingAmlChecks),
  isUserConverted:
    state.thirdPillar.exchangeableSourceFunds &&
    !state.thirdPillar.exchangeableSourceFunds.length &&
    state.login.userConversion &&
    state.login.userConversion.thirdPillar.selectionComplete,
});

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      onSign: exchangeActions.signMandate,
      onPreview: exchangeActions.previewMandate,
      onCancelSigningMandate: exchangeActions.cancelSigningMandate,
      onCloseErrorMessages: exchangeActions.closeErrorMessages,
    },
    dispatch,
  );

const wrapped = reduxForm({ form: 'confirmThirdPillarMandate' })(ConfirmThirdPillarMandate);

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(wrapped);
<commit_msg>Fix confirm 3rd pillar mandate validation<commit_after>import React from 'react';
import Types from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Link, Redirect } from 'react-router-dom';
import { Message } from 'retranslate';

import { reduxForm } from 'redux-form';
import ThirdPillarTermsAgreement from './ThirdPillarTermsAgreement';
import PoliticallyExposedPersonAgreement from '../../../aml/PoliticallyExposedPersonAgreement';
import { actions as exchangeActions } from '../../../exchange';
import FundTransferTable from '../../secondPillar/confirmMandate/fundTransferTable';
import ResidencyAgreement from '../../../aml/ResidencyAgreement';
import { AuthenticationLoader, ErrorMessage, Loader } from '../../../common';
import { hasAddress as isAddressFilled } from '../../../common/user/address';
import OccupationAgreement from '../../../aml/OccupationAgreement';
import { hasContactDetailsAmlCheck as isContactDetailsAmlCheckPassed } from '../../../aml';

export const ConfirmThirdPillarMandate = ({
  previousPath,
  nextPath,
  signedMandateId,
  exchangeExistingUnits,
  exchangeableSourceFunds,
  selectedFutureContributionsFund,
  agreedToTerms,
  isResident,
  isPoliticallyExposed,
  occupation,
  loadingSourceFunds,
  address,
  hasAddress,
  hasContactDetailsAmlCheck,
  isUserConverted,
  onSign,
  onPreview,
  onCancelSigningMandate,
  onCloseErrorMessages,
  loadingMandate,
  mandateSigningControlCode,
  mandateSigningError,
}) => {
  const buttonDisabled =
    !agreedToTerms || !isResident || !(isPoliticallyExposed === false) || !occupation;
  return (
    <>
      {isUserConverted && <Redirect to={nextPath} />}
      {signedMandateId && <Redirect to={nextPath} />}
      {(!hasAddress || !hasContactDetailsAmlCheck) && <Redirect to={previousPath} />}
      {loadingMandate || mandateSigningControlCode ? (
        <AuthenticationLoader
          controlCode={mandateSigningControlCode}
          onCancel={onCancelSigningMandate}
          overlayed
        />
      ) : (
        ''
      )}

      <Message>confirmThirdPillarMandate.intro</Message>

      {selectedFutureContributionsFund && (

          <Message>confirmThirdPillarMandate.contribution</Message>

            {selectedFutureContributionsFund.name}

      )}
      {loadingSourceFunds && <Loader className="align-middle" />}
      {exchangeExistingUnits &&
        !loadingSourceFunds &&
        exchangeableSourceFunds &&
        !!exchangeableSourceFunds.length &&
        selectedFutureContributionsFund && (

            <Message>confirmThirdPillarMandate.exchangeExistingUnits</Message>

              <FundTransferTable
                selections={createSelectionsFromFundsToFund(
                  exchangeableSourceFunds,
                  selectedFutureContributionsFund,
                )}
              />

        )}

      <ThirdPillarTermsAgreement />

      <PoliticallyExposedPersonAgreement className="mt-3" />
      <ResidencyAgreement className="mt-3" />
      <OccupationAgreement className="col-md-5 mt-3 px-0" />

      {mandateSigningError ? (
        <ErrorMessage errors={mandateSigningError.body} onCancel={onCloseErrorMessages} overlayed />
      ) : (
        ''
      )}

        <button
          type="button"
          className="btn btn-primary mb-2 mr-2"
          disabled={buttonDisabled}
          onClick={() => {
            onSign(
              getMandate(
                exchangeExistingUnits,
                exchangeableSourceFunds,
                selectedFutureContributionsFund,
                address,
              ),
              { isResident, isPoliticallyExposed, occupation },
            );
          }}
        >
          <Message>confirmThirdPillarMandate.sign</Message>
        </button>

        <button
          type="button"
          className="btn btn-secondary mb-2 mr-2"
          disabled={buttonDisabled}
          onClick={() => {
            onPreview(
              getMandate(
                exchangeExistingUnits,
                exchangeableSourceFunds,
                selectedFutureContributionsFund,
                address,
              ),
              { isResident, isPoliticallyExposed, occupation },
            );
          }}
        >
          <Message>confirmThirdPillarMandate.preview</Message>
        </button>

        <Link to={previousPath}>
          <button type="button" className="btn btn-secondary mb-2">
            <Message>confirmThirdPillarMandate.back</Message>
          </button>
        </Link>

    </>
  );
};

function getMandate(exchangeExistingUnits, sourceFunds, targetFund, address) {
  return {
    fundTransferExchanges: exchangeExistingUnits
      ? sourceFunds.map(sourceFund => ({
          amount: 1,
          sourceFundIsin: sourceFund.isin,
          targetFundIsin: targetFund.isin,
        }))
      : [],
    futureContributionFundIsin: targetFund.isin,
    address,
  };
}

function createSelectionsFromFundsToFund(sourceFunds, targetFund) {
  return sourceFunds.map(sourceFund => ({
    sourceFundIsin: sourceFund.isin,
    sourceFundName: sourceFund.name,
    targetFundIsin: targetFund.isin,
    targetFundName: targetFund.name,
    percentage: 1,
  }));
}

const fundType = Types.shape({ isin: Types.string, name: Types.string });

ConfirmThirdPillarMandate.propTypes = {
  previousPath: Types.string,
  nextPath: Types.string,

  loadingMandate: Types.bool,
  mandateSigningControlCode: Types.string,
  mandateSigningError: Types.string,
  signedMandateId: Types.number,
  exchangeExistingUnits: Types.bool,
  exchangeableSourceFunds: Types.arrayOf(fundType),
  selectedFutureContributionsFund: fundType,
  agreedToTerms: Types.bool,
  isResident: Types.bool,
  isPoliticallyExposed: Types.bool,
  occupation: Types.string,
  loadingSourceFunds: Types.bool,
  address: Types.shape({}),
  hasAddress: Types.bool,
  hasContactDetailsAmlCheck: Types.bool,
  isUserConverted: Types.bool,
  onSign: Types.func,
  onPreview: Types.func,
  onCancelSigningMandate: Types.func,
  onCloseErrorMessages: Types.func,
};

ConfirmThirdPillarMandate.defaultProps = {
  previousPath: '',
  nextPath: '',

  loadingMandate: false,
  mandateSigningControlCode: null,
  mandateSigningError: null,
  signedMandateId: null,
  exchangeExistingUnits: null,
  exchangeableSourceFunds: null,
  selectedFutureContributionsFund: {},
  agreedToTerms: false,
  isResident: null,
  isPoliticallyExposed: null,
  occupation: null,
  loadingSourceFunds: false,
  hasAddress: false,
  hasContactDetailsAmlCheck: false,
  address: {},
  isUserConverted: false,
  onSign: () => {},
  onPreview: () => {},
  onCancelSigningMandate: () => {},
  onCloseErrorMessages: () => {},
};

const mapStateToProps = state => ({
  loadingMandate: state.exchange.loadingMandate,
  mandateSigningControlCode: state.exchange.mandateSigningControlCode,
  mandateSigningError: state.exchange.mandateSigningError,
  signedMandateId: state.thirdPillar.signedMandateId,
  selectedFutureContributionsFund: state.thirdPillar.targetFunds.find(
    fund => fund.isin === state.thirdPillar.selectedFutureContributionsFundIsin,
  ),
  agreedToTerms: state.thirdPillar.agreedToTerms,
  isResident: state.aml.isResident,
  isPoliticallyExposed: state.aml.isPoliticallyExposed,
  occupation: state.aml.occupation,
  exchangeableSourceFunds: state.thirdPillar.exchangeableSourceFunds,
  exchangeExistingUnits: state.thirdPillar.exchangeExistingUnits,
  loadingSourceFunds: state.thirdPillar.loadingSourceFunds,
  address: (state.login.user || {}).address,
  hasAddress: !state.login.user || isAddressFilled(state.login.user),
  hasContactDetailsAmlCheck: isContactDetailsAmlCheckPassed(state.aml.missingAmlChecks),
  isUserConverted:
    state.thirdPillar.exchangeableSourceFunds &&
    !state.thirdPillar.exchangeableSourceFunds.length &&
    state.login.userConversion &&
    state.login.userConversion.thirdPillar.selectionComplete,
});

const mapDispatchToProps = dispatch =>
  bindActionCreators(
    {
      onSign: exchangeActions.signMandate,
      onPreview: exchangeActions.previewMandate,
      onCancelSigningMandate: exchangeActions.cancelSigningMandate,
      onCloseErrorMessages: exchangeActions.closeErrorMessages,
    },
    dispatch,
  );

const wrapped = reduxForm({ form: 'confirmThirdPillarMandate' })(ConfirmThirdPillarMandate);

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(wrapped);
<|endoftext|>


<commit_before>/*!
 * a2 - oauth2 browser client
 * Copyright(c) 2012 Matti Savolainen <matti@applifier.com>
 * MIT Licensed
 */

/***
 * In browser window is global, exports window.A2. Makes it easy to unit test this.
 */
(function(exports, global) {
  var A2 = exports;
  var options = {
      site : "https://local.host:3113"
    , state : +new Date()+""+Math.floor(Math.random() * 999999)
    , handle : "a2auth"
  };

  function accessToken(token) {
    var storage = global.localStorage;
    if (token === null) {
      return storage.removeItem("A2.accessToken");
    } else if (token === undefined) {
      return storage.getItem("A2.accessToken");
    } else {
      return storage.setItem("A2.accessToken", token);
    }
  }

  /**
   * dialog window for oauth.
   */
  var dialog = null;
  var token = accessToken();

  A2.initialize = function(cfg) {
    if(!cfg.client_id) {
     throw new Error("client_id is required!");
    }
    options.client_id = cfg.client_id;
    options.display = cfg.display || "popup";
    options.scope = cfg.scope || "basic";
    options.response_type = cfg.response_type || "token";
    options.redirect_uri = cfg.redirect_uri;
    options.width = cfg.width;
    options.height = cfg.height;
    var url = new A2.URL(document.location.search + document.location.hash);
    var params = url.parseParameters();
    if(params.access_token) {
      accessToken(params.access_token);
      document.location.href = document.location.search;
    }
  };

  var auth = A2.auth = null;
  A2.token = accessToken;
  var connectCallbacks = {
      success: function(token) {
        accessToken(token);
      }
    , error: function(error) {
      console.log(error);
    }
    , general: function(what) {
      console.log(what);
    }
    , disconnect: function() {
      accessToken(null);
    }
  };

  function URL(string) {
    this.url = string;
  }

  URL.prototype.mergeParams = function (url, params) {
    if(!params) {
      params = url;
      url = undefined;
    }
    if(!url) {
      url = this.url;
    }
    var baseURL = url.split("?")[0];
    var newParams = this.parseParameters(url.toString());
    params = params || {};
    for (var key in params) {
      if (params.hasOwnProperty(key)) {
        newParams[key] = params[key];
      }
    }
    var queryString = this.buildQueryString(newParams);
    if (queryString.length > 0) {
      queryString = "?" + queryString;
    }
    this.url = baseURL + queryString;
    return this.url;
  };

  URL.prototype.isRelative = function (url) {
    return url === undefined ? (this.url[0] === '/') : (url[0] === "/");
  };

  URL.prototype.buildQueryString = function (params) {
    var queryStringArray = [];
    for (var name in params) {
      if (params.hasOwnProperty(name)) {
        queryStringArray.push(encodeURIComponent(name) + "=" + encodeURIComponent(params[name]));
      }
    }
    return queryStringArray.join("&");
  };

  URL.prototype.open = function(opt, url) {
    url = url || this.url;
    opt = opt || options;
    var left = global.screenX + (global.outerWidth - opt.width) / 2;
    var top = global.screenY + (global.outerHeight - opt.height) / 2;
    var openstr = "location=1, width=" + opt.width + ", height=" + opt.height + ", top=" + top + ", left=" + left + ", toolbar=no,scrollbars=yes";
    this.w = global.open(url, opt.handle || options.handle, openstr);
    return this.w;
  };

  URL.prototype.navigate = function(url) {
    document.location.href = url || this.url;
  };

  URL.prototype.parseParameters = function (uri) {
    if(!uri) {
      uri = this.url;
    }
    var splitted = uri.split(/[&?#]/);
    if (splitted[0].match(/^http/)) {
      splitted.shift();
    }

    var obj = {};
    for (var i in splitted) {
      if (splitted.hasOwnProperty(i)) {
        var kv = splitted[i].split("=");
        if (kv[0]) {
          obj[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
        }
      }
    }
    return obj;
  };

  URL.prototype.get = function() {
    return this.url;
  };

  URL.prototype.secure = function(secure, url) {
    if(!url) {
      url = this.url;
    }
    if(secure === undefined) {
      return url.search(/^https:\/\//) !== -1;
    }
    if(secure === true) {
      url = url.replace(/^http:\/\//,"https://");
    }  else if (secure === false) {
      url = url.replace(/^https:\/\//,"http://");
    }
    return (this.url = url).search(/^https:\/\//) !==-1;
  };

  function Auth(opt) {
    this.token = opt.access_token || options.access_token || accessToken();
    this.client_id = opt.client_id || options.client_id;
    this.response_type = opt.response_type || options.response_type;
    if(options.redirect_uri || opt.redirect_uri) {
      this.redirect_uri = opt.redirect_uri || options.redirect_uri;
    }
    this.scope = opt.scope || options.scope;
    this.state = opt.state || options.state;
  }

  Auth.prototype.connectCallback = function() {
    var popup = A2.auth.handle;
    if(!popup) {
      popup = window;
    }
    var url = new URL(popup.location.search + popup.location.hash);
    var params = url.parseParameters();
    popup.close();

    if (params.error) {
      throw Error("OAuth2 error "+params.error.error_description || params.error.error);
    } else {
      if(this.callback) {
        return this.callback(decodeURIComponent(params.access_token));
      } else {
        return connectCallbacks.success(decodeURIComponent(params.access_token));
      }
    }
    connectCallbacks.general(params.error);
  };

  Auth.prototype.connect = function(callback) {
    connectCallbacks.success = callback;
    if(!this.client_id ) {
      throw new Error("Missing client_id!");
    }
    var url = this.url = new URL(options.site+"/oauth2/authorize");
    var params = {
        client_id:this.client_id
      , response_type:this.response_type
      , display: options.display
      , scope: this.scope
    };
    if(this.redirect_uri) {
      params.redirect_uri = this.redirect_uri;
    }

    url.mergeParams(params);

    this.callback = callback;
    if(params.display === "popup" && params.response_type === "token") {
      this.handle = url.open({
          width:640
        , height: 510
      });
    } else if(!params.display || params.display !== "popup") {
      document.location.href = url.get();
     }
  };

  /**
   * connects, ie gets access token if there is none and fetches player object.
   */
  A2.connect = function(callback) {
    var token = accessToken();
    if(token != null) {
      return callback(token);
    }
    if(!auth) {
      A2.auth = new Auth({});
    }

    A2.auth.connect(function(token) {
      accessToken(token);
      return callback(token);
    });
  }

  global.A2 = A2;
  global.A2.URL = URL;
})('object' === typeof module ? module.exports : (this.A2 = {}), "undefined" === typeof global ? window : global);<commit_msg>support site variable override<commit_after>/*!
 * a2 - oauth2 browser client
 * Copyright(c) 2012 Matti Savolainen <matti@applifier.com>
 * MIT Licensed
 */

/***
 * In browser window is global, exports window.A2. Makes it easy to unit test this.
 */
(function(exports, global) {
  var A2 = exports;
  var options = {
      site : "https://local.host:3113"
    , state : +new Date()+""+Math.floor(Math.random() * 999999)
    , handle : "a2auth"
  };

  function accessToken(token) {
    var storage = global.localStorage;
    if (token === null) {
      return storage.removeItem("A2.accessToken");
    } else if (token === undefined) {
      return storage.getItem("A2.accessToken");
    } else {
      return storage.setItem("A2.accessToken", token);
    }
  }

  /**
   * dialog window for oauth.
   */
  var dialog = null;
  var token = accessToken();

  A2.initialize = function(cfg) {
    console.log("initialize")
    if(!cfg.client_id) {
     throw new Error("client_id is required!");
    }
    options.client_id = cfg.client_id;
    options.display = cfg.display || "popup";
    options.scope = cfg.scope || "basic";
    options.response_type = cfg.response_type || "token";
    options.redirect_uri = cfg.redirect_uri;
    options.width = cfg.width;
    options.site = cfg.site || options.site;
    options.height = cfg.height;
    var url = new A2.URL(document.location.search + document.location.hash);
    var params = url.parseParameters();
    console.log(params);
    if(params.access_token) {
      accessToken(params.access_token);
      document.location.href = document.location.search;
    }
  };

  var auth = A2.auth = null;
  A2.token = accessToken;
  var connectCallbacks = {
      success: function(token) {
        accessToken(token);
      }
    , error: function(error) {
      console.log(error);
    }
    , general: function(what) {
      console.log(what);
    }
    , disconnect: function() {
      accessToken(null);
    }
  };

  function URL(string) {
    this.url = string;
  }

  URL.prototype.mergeParams = function (url, params) {
    if(!params) {
      params = url;
      url = undefined;
    }
    if(!url) {
      url = this.url;
    }
    var baseURL = url.split("?")[0];
    var newParams = this.parseParameters(url.toString());
    params = params || {};
    for (var key in params) {
      if (params.hasOwnProperty(key)) {
        newParams[key] = params[key];
      }
    }
    var queryString = this.buildQueryString(newParams);
    if (queryString.length > 0) {
      queryString = "?" + queryString;
    }
    this.url = baseURL + queryString;
    return this.url;
  };

  URL.prototype.isRelative = function (url) {
    return url === undefined ? (this.url[0] === '/') : (url[0] === "/");
  };

  URL.prototype.buildQueryString = function (params) {
    var queryStringArray = [];
    for (var name in params) {
      if (params.hasOwnProperty(name)) {
        queryStringArray.push(encodeURIComponent(name) + "=" + encodeURIComponent(params[name]));
      }
    }
    return queryStringArray.join("&");
  };

  URL.prototype.open = function(opt, url) {
    url = url || this.url;
    opt = opt || options;
    var left = global.screenX + (global.outerWidth - opt.width) / 2;
    var top = global.screenY + (global.outerHeight - opt.height) / 2;
    var openstr = "location=1, width=" + opt.width + ", height=" + opt.height + ", top=" + top + ", left=" + left + ", toolbar=no,scrollbars=yes";
    this.w = global.open(url, opt.handle || options.handle, openstr);
    return this.w;
  };

  URL.prototype.navigate = function(url) {
    document.location.href = url || this.url;
  };

  URL.prototype.parseParameters = function (uri) {
    if(!uri) {
      uri = this.url;
    }
    var splitted = uri.split(/[&?#]/);
    if (splitted[0].match(/^http/)) {
      splitted.shift();
    }

    var obj = {};
    for (var i in splitted) {
      if (splitted.hasOwnProperty(i)) {
        var kv = splitted[i].split("=");
        if (kv[0]) {
          obj[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
        }
      }
    }
    return obj;
  };

  URL.prototype.get = function() {
    return this.url;
  };

  URL.prototype.secure = function(secure, url) {
    if(!url) {
      url = this.url;
    }
    if(secure === undefined) {
      return url.search(/^https:\/\//) !== -1;
    }
    if(secure === true) {
      url = url.replace(/^http:\/\//,"https://");
    }  else if (secure === false) {
      url = url.replace(/^https:\/\//,"http://");
    }
    return (this.url = url).search(/^https:\/\//) !==-1;
  };

  function Auth(opt) {
    this.token = opt.access_token || options.access_token || accessToken();
    this.client_id = opt.client_id || options.client_id;
    this.response_type = opt.response_type || options.response_type;
    if(options.redirect_uri || opt.redirect_uri) {
      this.redirect_uri = opt.redirect_uri || options.redirect_uri;
    }
    this.scope = opt.scope || options.scope;
    this.state = opt.state || options.state;
  }

  Auth.prototype.connectCallback = function() {
    var popup = A2.auth.handle;
    if(!popup) {
      popup = window;
    }
    var url = new URL(popup.location.search + popup.location.hash);
    var params = url.parseParameters();
    popup.close();

    if (params.error) {
      throw Error("OAuth2 error "+params.error.error_description || params.error.error);
    } else {
      if(this.callback) {
        return this.callback(decodeURIComponent(params.access_token));
      } else {
        return connectCallbacks.success(decodeURIComponent(params.access_token));
      }
    }
    connectCallbacks.general(params.error);
  };

  Auth.prototype.connect = function(callback) {
    connectCallbacks.success = callback;
    if(!this.client_id ) {
      throw new Error("Missing client_id!");
    }
    var url = this.url = new URL(options.site+"/oauth2/authorize");
    var params = {
        client_id:this.client_id
      , response_type:this.response_type
      , display: options.display
      , scope: this.scope
    };
    if(this.redirect_uri) {
      params.redirect_uri = this.redirect_uri;
    }

    url.mergeParams(params);

    this.callback = callback;
    if(params.display === "popup" && params.response_type === "token") {
      this.handle = url.open({
          width: 640
        , height: 510
      });
    } else if(!params.display || params.display !== "popup") {
      document.location.href = url.get();
     }
  };

  /**
   * connects, ie gets access token if there is none and fetches player object.
   */
  A2.connect = function(callback) {
    var token = accessToken();
    if(token != null) {
      return callback(token);
    }
    if(!auth) {
      A2.auth = new Auth({});
    }

    A2.auth.connect(function(token) {
      accessToken(token);
      return callback(token);
    });
  }

  global.A2 = A2;
  global.A2.URL = URL;

})('object' === typeof module ? module.exports : (this.A2 = {}), "undefined" === typeof global ? window : global);<|endoftext|>


<commit_before>function RGBColor(r,g,b) {
    this.r = r;
    this.g = g;
    this.b = b;
}

RGBColor.prototype.asString = function () {
    return "rgb(" + this.r + "," + this.g + "," + this.b + ")";
}

RGBColor.prototype.invert = function() {
    return new RGBColor(255 - this.r, 255 - this.g, 255 - this.b);
}

RGBColor.prototype.darkenBy = function(amount) {
    var darker = new RGBColor(this.r,this.g,this.b);

    darker.r -= amount;
    darker.g -= amount;
    darker.b -= amount;

    if (darker.r < 0) {
        darker.r = 0;
    }

    if (darker.g < 0) {
        darker.g = 0;
    }

    if (darker.b < 0) {
        darker.b = 0;
    }

    return darker;
}

RGBColor.prototype.lightenBy = function(amount) {
    var darker = new RGBColor(this.r,this.g,this.b);

    darker.r += amount;
    darker.g += amount;
    darker.b += amount;

    if (darker.r > 255) {
        darker.r = 255;
    }

    if (darker.g > 255) {
        darker.g = 255;
    }

    if (darker.b > 255) {
        darker.b = 255;
    }

    return darker;
}

RGBColor.prototype.subtract = function(c) {
	return new RGBColor(
		this.r - c.r,
		this.g - c.g,
		this.b - c.b
	);
}<commit_msg>added asStringWithAlpha method<commit_after>function RGBColor(r,g,b) {
    this.r = r;
    this.g = g;
    this.b = b;
}

RGBColor.prototype.asString = function () {
    return "rgb(" + this.r + "," + this.g + "," + this.b + ")";
}

RGBColor.prototype.asStringWithAlpha = function (alpha) {
    return "rgba(" + this.r + "," + this.g + "," + this.b + ',' + alpha + ")";
}

RGBColor.prototype.invert = function() {
    return new RGBColor(255 - this.r, 255 - this.g, 255 - this.b);
}

RGBColor.prototype.darkenBy = function(amount) {
    var darker = new RGBColor(this.r,this.g,this.b);

    darker.r -= amount;
    darker.g -= amount;
    darker.b -= amount;

    if (darker.r < 0) {
        darker.r = 0;
    }

    if (darker.g < 0) {
        darker.g = 0;
    }

    if (darker.b < 0) {
        darker.b = 0;
    }

    return darker;
}

RGBColor.prototype.lightenBy = function(amount) {
    var darker = new RGBColor(this.r,this.g,this.b);

    darker.r += amount;
    darker.g += amount;
    darker.b += amount;

    if (darker.r > 255) {
        darker.r = 255;
    }

    if (darker.g > 255) {
        darker.g = 255;
    }

    if (darker.b > 255) {
        darker.b = 255;
    }

    return darker;
}

RGBColor.prototype.subtract = function(c) {
	return new RGBColor(
		this.r - c.r,
		this.g - c.g,
		this.b - c.b
	);
}
<|endoftext|>


<commit_before>'use strict';

mage.directive('pdcObservation', ['FeatureTypeService' , function (FeatureTypeService) {

  return {
    restrict: "A",
    templateUrl: "/js/app/partials/pdc/observation.html",
    controller: "FeatureController",
    link: function(scope) {

      scope.teams = [
        'NW',
        'NE',
        'SE',
        'SW'
      ];

      scope.levels = [{
        name: 'None',
        color: 'blue'
      },{
        name: 'Low',
        color: 'green'
      },{
        name: 'Medium',
        color: 'yellow'
      },{
        name: 'High',
        color: 'red'
      }];

      FeatureTypeService.getAllFeatureTypes().
      success(function (types, status, headers, config) {
        scope.types = types;
      }).
      error(function (data, status, headers, config) {
        console.log("Error getting types: " + status);
      });

      /*
      scope.types = [{
        name: 'At Venue',
        icon: 'building'
      },{
        name: 'Protest',
        icon: 'fist'
      },{
        name: 'Other Event',
        icon: 'calendar'
      },{
        name: 'Parade Event',
        icon: 'ballon'
      },{
        name: 'CBRN Hazard',
        icon: 'radio-active'
      },{
        name: 'Crowd',
        icon: 'people'
      },{
        name: 'Explosion',
        icon: 'new'
      },{
        name: 'Fire',
        icon: 'fire'
      },{
        name: 'Medical Incident',
        icon: 'ambulance'
      },{
        name: 'Transportation Incident',
        icon: 'warning-sign'
      },{
        name: 'Other Activity',
       icon: 'activity'
      },{
        name: 'Shots Fired',
        icon: 'gun'
      },{
        name: 'Significant Incident',
        icon: 'asterisk'
      },{
        name: 'Suspicious Individual',
        icon: 'running'
      },{
        name: 'Suspicious Vehicle',
        icon: 'car'
      },{
        name: 'Suspicious Package',
        icon: 'gift'
      },{
        name: 'Violent Activity',
        icon: 'hit'
      },{
        name: 'Remain Over Night',
        icon: 'moon-fill'
      },{
        name: 'Arrival',
        icon: 'login'
      },{
        name: 'Departure',
        icon: 'logout'
      },{
        name: 'Aeronautical Incident',
        icon: 'airplane'
      },{
        name: 'Maritime Incident',
        icon: 'anchor'
      },{
        name: 'Evacuation',
        icon: 'ban-circle'
      },{
        name: 'Hostage',
        icon: 'hostage'
      },{
        name: 'Kidnapping',
        icon: 'kidnap'
      },{
        name: 'VIP',
        icon: 'star'
      }];
      */

      scope.createNewObservation = function(location) {
        return {
          attributes: {
            TYPE: scope.types[0].name,
            EVENTLEVEL: scope.levels[0].name,
            TEAM: scope.teams[0]
          }
        };
      }
    }
  };
}]);<commit_msg>removed dead code...<commit_after>'use strict';

mage.directive('pdcObservation', ['FeatureTypeService' , function (FeatureTypeService) {

  return {
    restrict: "A",
    templateUrl: "/js/app/partials/pdc/observation.html",
    controller: "FeatureController",
    link: function(scope) {

      scope.teams = [
        'NW',
        'NE',
        'SE',
        'SW'
      ];

      scope.levels = [{
        name: 'None',
        color: 'blue'
      },{
        name: 'Low',
        color: 'green'
      },{
        name: 'Medium',
        color: 'yellow'
      },{
        name: 'High',
        color: 'red'
      }];

      //Get the Feature Types
      FeatureTypeService.getAllFeatureTypes().
      success(function (types, status, headers, config) {
        scope.types = types;
      }).
      error(function (data, status, headers, config) {
        console.log("Error getting types: " + status);
      });

      scope.createNewObservation = function(location) {
        return {
          attributes: {
            TYPE: scope.types[0].name,
            EVENTLEVEL: scope.levels[0].name,
            TEAM: scope.teams[0]
          }
        };
      }
    }
  };
}]);<|endoftext|>