<commit_before>/**
 * Angular minieditor
 * Code licensed under the MIT License:
 * https://github.com/jerryhsia/angular-minieditor/blob/master/LICENSE
 *
 * @version 1.0.1
 * @author  Jerry Hsia (xiajie9916@gmail.com)
 * @description A mini text editor for angularjs.
 */

'use strict';
(function(){
  var miniCommands = {
    bold: {
      command: 'bold',
      type: 'button',
      title: 'Bold',
      icon: 'bold',
      arg: false
    },
    italic: {
      command: 'italic',
      type: 'button',
      title: 'Italic',
      icon: 'italic',
      arg: false
    },
    underline: {
      command: 'underline',
      type: 'button',
      title: 'Underline',
      icon: 'underline',
      arg: false
    },
    strikethrough: {
      command: 'strikethrough',
      type: 'button',
      title: 'Strikethrough',
      icon: 'strikethrough',
      arg: false
    },
    justifyleft: {
      command: 'justifyleft',
      type: 'button',
      title: 'Left justify',
      icon: 'align-left',
      arg: false
    },
    justifycenter: {
      command: 'justifycenter',
      type: 'button',
      title: 'Center justify',
      icon: 'align-center',
      arg: false
    },
    justifyright: {
      command: 'justifyright',
      type: 'button',
      title: 'Right justify',
      icon: 'align-right',
      arg: false
    },
    insertunorderedlist: {
      command: 'insertunorderedlist',
      type: 'button',
      title: 'Unordered list',
      icon: 'list-ul',
      arg: false
    },
    insertorderedlist: {
      command: 'insertorderedlist',
      type: 'button',
      title: 'Ordered list',
      icon: 'list-ol',
      arg: false
    },
    indent: {
      command: 'indent',
      type: 'button',
      title: 'Indent',
      icon: 'indent',
      arg: false
    },
    outdent: {
      command: 'outdent',
      type: 'button',
      title: 'Outdent',
      icon: 'outdent',
      arg: false
    },
    removeformat: {
      command: 'removeformat',
      type: 'button',
      title: 'Remove format',
      icon: 'eraser',
      arg: false
    },
    fontsize: {
      command: 'fontsize',
      type: 'button',
      title: 'Font size',
      icon: 'text-height',
      arg: true,
      promptTitle: 'Enter the number of 1-7',
      defaultValue: 3,
      filter: function(value) {
        value = value ? parseInt(value) : false;
        if (!(value >= 1 && value <= 7)) {
          value = 2;
        }
        return value;
      }
    },
    createlink: {
      command: 'createlink',
      type: 'button',
      title: 'Create link',
      icon: 'link',
      arg: true,
      promptTitle: 'Enter the link url',
      defaultValue: 'http://',
      filter: function(value) {
        return value;
      }
    },
    insertimage: {
      command: 'insertimage',
      type: 'button',
      title: 'Insert image',
      icon: 'image',
      arg: true,
      promptTitle: 'Enter the image url',
      defaultValue: 'http://',
      filter: function(value) {
        return value;
      }
    }
  };

  var defaultMenus = [
    ['bold', 'italic', 'underline', 'strikethrough'],
    ['fontsize'],
    ['justifyleft', 'justifycenter', 'justifyright'],
    ['insertunorderedlist', 'insertorderedlist'],
    ['indent', 'outdent'],
    ['removeformat', 'createlink', 'insertimage']
  ];

  angular.module('jerryhsia.minieditor', []).directive('minieditor', function($compile, $timeout, minieditorUI) {
    function link($scope, $element, $attrs, $ctrl) {
      if (!angular.isDefined($scope.options) || !angular.isObject($scope.options)) {
        $scope.options = {};
      }
      $element.html(minieditorUI.getTemplate($scope.options));
      $compile($element.contents())($scope);

      var editor = $element.find('div.minieditor-content');

      $ctrl.$render = function() {
        editor.html($ctrl.$viewValue);
      };

      editor.on('input keyup paste mouseup', function(event) {
        var content = editor.html();
        if (content == '
') {
          content = '';
        }
        $ctrl.$setViewValue(content);
      });

      function getCommandState(command) {
        return document.queryCommandState(command);
      }

      function getCommandValue(command) {
        return document.queryCommandValue(command);
      }

      function isTag(tag) {
        var selection = window.getSelection().getRangeAt(0);
        if (selection) {
          if (selection.startContainer.parentNode.tagName === tag.toUpperCase()
            || selection.endContainer.parentNode.tagName === tag.toUpperCase()
          ) {
            return true;
          } else {
            return false;
          }
        } else {
          return false;
        }
      }

      $scope.states = {};
      var refreshing = false;
      function refreshState() {
        if (refreshing) return;
        refreshing = true;
        angular.forEach(miniCommands, function(commandObj, command) {
          if (commandObj.arg) {
            var value = getCommandValue(command);
            if (command == 'createlink') {
              value = isTag('a');
            }

            if (value) {
              $scope.states[command] = value;
            } else {
              delete $scope.states[command];
            }
          } else {
            var state = getCommandState(command);
            if (state) {
              $scope.states[command] = state;
            } else {
              delete $scope.states[command];
            }
          }
        });
        refreshing = false;
      }

      $scope.exec = function(command) {
        var commandObj = miniCommands[command];
        if (commandObj.arg) {
          $timeout(function() {
            var defaultValue = getCommandValue(command);
            if (!defaultValue) {
              defaultValue = commandObj.defaultValue;
            }
            var value = prompt(commandObj.promptTitle, defaultValue);
            if (value && value.length > 0) {
              value = commandObj.filter(value);
              document.execCommand(command, false, value);
              refreshState();
            }
          }, 50);
        } else {
          document.execCommand(command, false, null);
          refreshState();
        }
      };

      editor.on('click keyup focus mouseup blur', function() {
        $timeout(function() {
          refreshState();
        }, 50);
      });
    }

    return {
      template: '

',
      restrict: 'E',
      scope: {
        value: '=ngModel',
        options: '=options'
      },
      replace: true,
      require: 'ngModel',
      link: link
    };
  }).factory('minieditorUI', function() {

    function getTemplate(options) {
      return getMenuTemplate(options) + getContentTemplate(options);
    }

    function getContentTemplate(options) {
      return '

';
    }

    function getMenuTemplate(options) {
      var menus;
      if (angular.isDefined(options.menus) && angular.isArray(options.menus)) {
        menus = options.menus;
      } else {
        menus = defaultMenus;
      }
      var menuText = '
';
      for (var i = 0; i < menus.length; i++) {
        menuText += '
';
        for (var j = 0; j < menus[i].length; j++) {
          if (angular.isDefined(miniCommands[menus[i][j]])) {
            menuText += getMenuItem(miniCommands[menus[i][j]]);
          } else {
            console.log('Unknow command: ' + menus[i][j]);
          }
        }
        menuText += '
';
      }
      menuText += '
';
      return menuText;
    }

    function getMenuItem(commandObj) {
      switch (commandObj.type) {
        case 'button':
          return '<button ng-click="exec(\'' + commandObj.command + '\')" ng-class="{active: states[\''+commandObj.command+'\']}" type="button" class="btn btn-default" title="'+commandObj.title+'" unselectable="on"></button>';
          break;
        default:
          return '';
          break;
      }
    }

    return {
      getTemplate: getTemplate
    };
  });
})();
<commit_msg>Update<commit_after>/**
 * Angular minieditor
 * Code licensed under the MIT License:
 * https://github.com/jerryhsia/angular-minieditor/blob/master/LICENSE
 *
 * @version 1.0.1
 * @author  Jerry Hsia (jerry9916@qq.com)
 * @description A mini text editor for angularjs.
 */

'use strict';
(function(){
  var miniCommands = {
    bold: {
      command: 'bold',
      type: 'button',
      title: 'Bold',
      icon: 'bold',
      arg: false
    },
    italic: {
      command: 'italic',
      type: 'button',
      title: 'Italic',
      icon: 'italic',
      arg: false
    },
    underline: {
      command: 'underline',
      type: 'button',
      title: 'Underline',
      icon: 'underline',
      arg: false
    },
    strikethrough: {
      command: 'strikethrough',
      type: 'button',
      title: 'Strikethrough',
      icon: 'strikethrough',
      arg: false
    },
    justifyleft: {
      command: 'justifyleft',
      type: 'button',
      title: 'Left justify',
      icon: 'align-left',
      arg: false
    },
    justifycenter: {
      command: 'justifycenter',
      type: 'button',
      title: 'Center justify',
      icon: 'align-center',
      arg: false
    },
    justifyright: {
      command: 'justifyright',
      type: 'button',
      title: 'Right justify',
      icon: 'align-right',
      arg: false
    },
    insertunorderedlist: {
      command: 'insertunorderedlist',
      type: 'button',
      title: 'Unordered list',
      icon: 'list-ul',
      arg: false
    },
    insertorderedlist: {
      command: 'insertorderedlist',
      type: 'button',
      title: 'Ordered list',
      icon: 'list-ol',
      arg: false
    },
    indent: {
      command: 'indent',
      type: 'button',
      title: 'Indent',
      icon: 'indent',
      arg: false
    },
    outdent: {
      command: 'outdent',
      type: 'button',
      title: 'Outdent',
      icon: 'outdent',
      arg: false
    },
    removeformat: {
      command: 'removeformat',
      type: 'button',
      title: 'Remove format',
      icon: 'eraser',
      arg: false
    },
    fontsize: {
      command: 'fontsize',
      type: 'button',
      title: 'Font size',
      icon: 'text-height',
      arg: true,
      promptTitle: 'Enter the number of 1-7',
      defaultValue: 3,
      filter: function(value) {
        value = value ? parseInt(value) : false;
        if (!(value >= 1 && value <= 7)) {
          value = 2;
        }
        return value;
      }
    },
    createlink: {
      command: 'createlink',
      type: 'button',
      title: 'Create link',
      icon: 'link',
      arg: true,
      promptTitle: 'Enter the link url',
      defaultValue: 'http://',
      filter: function(value) {
        return value;
      }
    },
    insertimage: {
      command: 'insertimage',
      type: 'button',
      title: 'Insert image',
      icon: 'image',
      arg: true,
      promptTitle: 'Enter the image url',
      defaultValue: 'http://',
      filter: function(value) {
        return value;
      }
    }
  };

  var defaultMenus = [
    ['bold', 'italic', 'underline', 'strikethrough'],
    ['fontsize'],
    ['justifyleft', 'justifycenter', 'justifyright'],
    ['insertunorderedlist', 'insertorderedlist'],
    ['indent', 'outdent'],
    ['removeformat', 'createlink', 'insertimage']
  ];

  angular.module('jerryhsia.minieditor', []).directive('minieditor', function($compile, $timeout, minieditorUI) {
    function link($scope, $element, $attrs, $ctrl) {
      if (!angular.isDefined($scope.options) || !angular.isObject($scope.options)) {
        $scope.options = {};
      }
      $element.html(minieditorUI.getTemplate($scope.options));
      $compile($element.contents())($scope);

      var editor = $element.find('div.minieditor-content');

      $ctrl.$render = function() {
        editor.html($ctrl.$viewValue);
      };

      editor.on('input keyup paste mouseup', function(event) {
        var content = editor.html();
        if (content == '
') {
          content = '';
        }
        $ctrl.$setViewValue(content);
      });

      function getCommandState(command) {
        return document.queryCommandState(command);
      }

      function getCommandValue(command) {
        return document.queryCommandValue(command);
      }

      function isTag(tag) {
        var selection = window.getSelection().getRangeAt(0);
        if (selection) {
          if (selection.startContainer.parentNode.tagName === tag.toUpperCase()
            || selection.endContainer.parentNode.tagName === tag.toUpperCase()
          ) {
            return true;
          } else {
            return false;
          }
        } else {
          return false;
        }
      }

      $scope.states = {};
      var refreshing = false;
      function refreshState() {
        if (refreshing) return;
        refreshing = true;
        angular.forEach(miniCommands, function(commandObj, command) {
          if (commandObj.arg) {
            var value = getCommandValue(command);
            if (command == 'createlink') {
              value = isTag('a');
            }

            if (value) {
              $scope.states[command] = value;
            } else {
              delete $scope.states[command];
            }
          } else {
            var state = getCommandState(command);
            if (state) {
              $scope.states[command] = state;
            } else {
              delete $scope.states[command];
            }
          }
        });
        refreshing = false;
      }

      $scope.exec = function(command) {
        var commandObj = miniCommands[command];
        if (commandObj.arg) {
          $timeout(function() {
            var defaultValue = getCommandValue(command);
            if (!defaultValue) {
              defaultValue = commandObj.defaultValue;
            }
            var value = prompt(commandObj.promptTitle, defaultValue);
            if (value && value.length > 0) {
              value = commandObj.filter(value);
              document.execCommand(command, false, value);
              refreshState();
            }
          }, 50);
        } else {
          document.execCommand(command, false, null);
          refreshState();
        }
      };

      editor.on('click keyup focus mouseup blur', function() {
        $timeout(function() {
          refreshState();
        }, 50);
      });
    }

    return {
      template: '

',
      restrict: 'E',
      scope: {
        value: '=ngModel',
        options: '=options'
      },
      replace: true,
      require: 'ngModel',
      link: link
    };
  }).factory('minieditorUI', function() {

    function getTemplate(options) {
      return getMenuTemplate(options) + getContentTemplate(options);
    }

    function getContentTemplate(options) {
      return '

';
    }

    function getMenuTemplate(options) {
      var menus;
      if (angular.isDefined(options.menus) && angular.isArray(options.menus)) {
        menus = options.menus;
      } else {
        menus = defaultMenus;
      }
      var menuText = '
';
      for (var i = 0; i < menus.length; i++) {
        menuText += '
';
        for (var j = 0; j < menus[i].length; j++) {
          if (angular.isDefined(miniCommands[menus[i][j]])) {
            menuText += getMenuItem(miniCommands[menus[i][j]]);
          } else {
            console.log('Unknow command: ' + menus[i][j]);
          }
        }
        menuText += '
';
      }
      menuText += '
';
      return menuText;
    }

    function getMenuItem(commandObj) {
      switch (commandObj.type) {
        case 'button':
          return '<button ng-click="exec(\'' + commandObj.command + '\')" ng-class="{active: states[\''+commandObj.command+'\']}" type="button" class="btn btn-default" title="'+commandObj.title+'" unselectable="on"></button>';
          break;
        default:
          return '';
          break;
      }
    }

    return {
      getTemplate: getTemplate
    };
  });
})();
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import './index.css';
import App from './App';

function loadPage(contents, estimatedTime, reward) {
  ReactDOM.render(<App contents={contents} estimatedTime={estimatedTime} reward={reward} />, document.getElementById('root'));
}

const url = new URL(window.location);
const estimatedTime = document.getElementById('input-time').value;
const reward = document.getElementById('input-reward').value;
const rawContents = document.getElementById('input-document').value;

// Better mechanism to select which to render.
if (url.pathname.match(/\/([0-9]+)/)) {
  let fname = url.pathname.match(/\/([0-9]+)/)[1];
  console.log("Getting file " + fname);
  axios.get("/data/pilot-20170810/" + fname + ".json")
    .then((result) => {
      let contents = result.data;
      loadPage(contents, estimatedTime, reward);
    })
} else
  if (rawContents.length > 0 && rawContents[0] !== '$') {
  let contents = JSON.parse(rawContents);
  loadPage(contents, estimatedTime, reward);
} else {
  let contents = {
    "title": "Title",
    "paragraphs": ["A line of text."],
    "viewSelections": [],
    "recommendedMinWordCount": 10,
    "recommendedMaxWordCount": 50,
    "prompt": "",
  };
  loadPage(contents, estimatedTime, reward);
}

// Some hijinks for AMT
if (document.getElementById('submitButton') !== null) {
  document.getElementById('submitButton').remove();
}
<commit_msg>Point index to newest app<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import './index.css';
import App from './App';

function loadPage(contents, estimatedTime, reward) {
  ReactDOM.render(<App contents={contents} estimatedTime={estimatedTime} reward={reward} />, document.getElementById('root'));
}

const url = new URL(window.location);
const estimatedTime = document.getElementById('input-time').value;
const reward = document.getElementById('input-reward').value;
const rawContents = document.getElementById('input-document').value;

// Better mechanism to select which to render.
if (url.pathname.match(/\/([0-9]+)/)) {
  let fname = url.pathname.match(/\/([0-9]+)/)[1];
  console.log("Getting file " + fname);
  axios.get("/data/pilot-20170817/" + fname + ".json")
    .then((result) => {
      let contents = result.data;
      loadPage(contents, estimatedTime, reward);
    })
} else
  if (rawContents.length > 0 && rawContents[0] !== '$') {
  let contents = JSON.parse(rawContents);
  loadPage(contents, estimatedTime, reward);
} else {
  let contents = {
    "title": "Title",
    "paragraphs": ["A line of text."],
    "viewSelections": [],
    "recommendedMinWordCount": 10,
    "recommendedMaxWordCount": 50,
    "prompt": "",
  };
  loadPage(contents, estimatedTime, reward);
}

// Some hijinks for AMT
if (document.getElementById('submitButton') !== null) {
  document.getElementById('submitButton').remove();
}
<|endoftext|>


<commit_before>/**
 * This script finds all the `import` and `require` of a packages
 * and checks that there's a dependency for each one of them in
 * the `package.json`.
 */

const fs = require('fs-extra');
const path = require('path');
const globby = require('globby');
const builtIn = require('builtin-modules');

const typeDependencies = new Set([
    'har-format',
    'estree',
    'request'
]);

const getPackages = async () => {
    const pkgs = await globby(['packages/*'], {
        absolute: true,
        gitignore: true,
        onlyFiles: false
    });

    console.log(`Packages found: ${pkgs.length}`);

    return pkgs;
};

const getDependencies = (pkg) => {
    const depArray = [
        ...builtIn,
        ...Object.keys(pkg.dependencies || []),
        ...Object.keys(pkg.devDependencies || []),
        ...Object.keys(pkg.peerDependencies || []),
        ...Object.keys(pkg.optionalDependencies || [])
    ];

    const dependencies = new Set(depArray);

    return dependencies;
};

const getCodeContent = async (pkgPath) => {
    const files = await globby([
        `${pkgPath}/**/*.ts`,
        `${pkgPath}/**/*.tsx`,
        `${pkgPath}/**/*.js`,
        `${pkgPath}/**/*.jsx`,
        `!${pkgPath}/**/*.d.ts`,
        `!${pkgPath}/node_modules/**/*` // needed when we are inside a package like extension-vscode
    ], { gitignore: true });

    const contents = new Map();

    const readPromises = files.map((file) => {
        return fs.readFile(file, 'utf-8')
            .then((content) => {
                contents.set(file, content);
            });
    });

    await Promise.all(readPromises);

    return contents;
};

const processFile = (content, dependencies) => {
    const missingDependencies = new Set();
    const regexps = [
        /import\s+.*?\s+'([a-z0-9_-]+)';/gi, // `import * as something from 'something';`
        /import\('([a-z0-9_-]+)'\)/gi, // `import('something');`
        /require\('([a-z0-9_-]+)'\);/gi // `const something = require('something');`
    ];

    regexps.forEach((regex) => {
        let match;

        while ((match = regex.exec(content)) !== null) {
            const [, dependency] = match;

            /**
             * Only the first part of the dependecy is needed:
             * E.g.: `import * from 'hint/dist/src/types'`
             *        `hint`
             */
            const root = dependency.split('/').shift();

            if (!dependencies.has(root) && !(typeDependencies.has(root) && dependencies.has(`@types/${root}`))) {
                missingDependencies.add(root);
            }
        }
    });

    return missingDependencies;
};

const processPackage = async (pkgPath) => {
    /**
     * 1. Load package.json and get the dependencies, devDependencies and peerDependencies
     * 2. Find all *.ts and *.js files ignoring node_modules
     * 3. For each file search all imports and requires
     * 4. Match content against dependencies
     */

    const pkgJsonPath = path.join(pkgPath, 'package.json');
    const pkg = require(pkgJsonPath);
    const dependencies = getDependencies(pkg);
    const files = await getCodeContent(pkgPath);

    if (files.size === 0) {
        console.log(`No files to process for "${pkgPath}"`);

        return;
    }

    console.log(`Processing "${pkgPath}"`);

    for (const [filePath, content] of files) {

        const missingDependencies = processFile(content, dependencies);

        if (missingDependencies.size > 0) {
            console.error(`\t${filePath}`);
            missingDependencies.forEach((dependency) => {
                console.error(`\t\t${dependency}`);
            });

            process.exitCode = 1;
        }
    }
};

const init = async () => {

    /**
     * If running from the root need to lint all packages,
     * otherwise just the current package
     */
    const pkgs = process.cwd() === path.join(__dirname, '..') ?
        await getPackages() :
        [process.cwd()];

    for (const pkg of pkgs) {
        await processPackage(pkg);
    }
};

init();
<commit_msg>Build: Do not lint dependencies under `dist`<commit_after>/**
 * This script finds all the `import` and `require` of a packages
 * and checks that there's a dependency for each one of them in
 * the `package.json`.
 */

const fs = require('fs-extra');
const path = require('path');
const globby = require('globby');
const builtIn = require('builtin-modules');

const typeDependencies = new Set([
    'har-format',
    'estree',
    'request'
]);

const getPackages = async () => {
    const pkgs = await globby(['packages/*'], {
        absolute: true,
        gitignore: true,
        onlyFiles: false
    });

    console.log(`Packages found: ${pkgs.length}`);

    return pkgs;
};

const getDependencies = (pkg) => {
    const depArray = [
        ...builtIn,
        ...Object.keys(pkg.dependencies || []),
        ...Object.keys(pkg.devDependencies || []),
        ...Object.keys(pkg.peerDependencies || []),
        ...Object.keys(pkg.optionalDependencies || [])
    ];

    const dependencies = new Set(depArray);

    return dependencies;
};

const getCodeContent = async (pkgPath) => {
    const files = await globby([
        `${pkgPath}/**/*.ts`,
        `${pkgPath}/**/*.tsx`,
        `${pkgPath}/**/*.js`,
        `${pkgPath}/**/*.jsx`,
        `!${pkgPath}/**/*.d.ts`,
        `!${pkgPath}/dist/**/*`,
        `!${pkgPath}/node_modules/**/*` // needed when we are inside a package like extension-vscode
    ], { gitignore: true });

    const contents = new Map();

    const readPromises = files.map((file) => {
        return fs.readFile(file, 'utf-8')
            .then((content) => {
                contents.set(file, content);
            });
    });

    await Promise.all(readPromises);

    return contents;
};

const processFile = (content, dependencies) => {
    const missingDependencies = new Set();
    const regexps = [
        /import\s+.*?\s+'([a-z0-9_-]+)';/gi, // `import * as something from 'something';`
        /import\('([a-z0-9_-]+)'\)/gi, // `import('something');`
        /require\('([a-z0-9_-]+)'\);/gi // `const something = require('something');`
    ];

    regexps.forEach((regex) => {
        let match;

        while ((match = regex.exec(content)) !== null) {
            const [, dependency] = match;

            /**
             * Only the first part of the dependecy is needed:
             * E.g.: `import * from 'hint/dist/src/types'`
             *        `hint`
             */
            const root = dependency.split('/').shift();

            if (!dependencies.has(root) && !(typeDependencies.has(root) && dependencies.has(`@types/${root}`))) {
                missingDependencies.add(root);
            }
        }
    });

    return missingDependencies;
};

const processPackage = async (pkgPath) => {
    /**
     * 1. Load package.json and get the dependencies, devDependencies and peerDependencies
     * 2. Find all *.ts and *.js files ignoring node_modules
     * 3. For each file search all imports and requires
     * 4. Match content against dependencies
     */

    const pkgJsonPath = path.join(pkgPath, 'package.json');
    const pkg = require(pkgJsonPath);
    const dependencies = getDependencies(pkg);
    const files = await getCodeContent(pkgPath);

    if (files.size === 0) {
        console.log(`No files to process for "${pkgPath}"`);

        return;
    }

    console.log(`Processing "${pkgPath}"`);

    for (const [filePath, content] of files) {

        const missingDependencies = processFile(content, dependencies);

        if (missingDependencies.size > 0) {
            console.error(`\t${filePath}`);
            missingDependencies.forEach((dependency) => {
                console.error(`\t\t${dependency}`);
            });

            process.exitCode = 1;
        }
    }
};

const init = async () => {

    /**
     * If running from the root need to lint all packages,
     * otherwise just the current package
     */
    const pkgs = process.cwd() === path.join(__dirname, '..') ?
        await getPackages() :
        [process.cwd()];

    for (const pkg of pkgs) {
        await processPackage(pkg);
    }
};

init();
<|endoftext|>


<commit_before>import { createAction, handleActions } from 'redux-actions'
import { readMyReputation } from './reputation'
// Helpers
import { get } from 'lodash'
import { getChluAPIClient } from 'helpers/chlu'
import { createDAGNode } from 'chlu-api-client/src/utils/ipfs'
import { getFormValues } from 'redux-form'

// Constants
const CRAWLER_START = 'crawler/START'
const CRAWLER_ERROR = 'crawler/ERROR'
const CRAWLER_FINISH = 'crawler/FINISH'

const API_URL = process.env.REACT_APP_CHLU_PUBLISH_URL || 'https://publish.chlu.io'
const CRAWLER_PROGRESS_POLL_INTERVAL = 5000 // ms

function getInitialState() {
  return {
    running: false,
    savingToIPFS: false,
    error: null
  }
}

export const crawlerError = createAction(CRAWLER_ERROR)
const startCrawlerAction = createAction(CRAWLER_START)
export const finishCrawler = createAction(CRAWLER_FINISH)

export function startCrawler(type, url, username, password) {
  return async (dispatch, getState) => {
    const state = getState()
    try {
      const signedInDid = get(state, 'data.wallet.did', null)
      const signedOutDid = get(state, 'components.createWallet.walletCreated.did', null)
      const did = signedInDid || signedOutDid
      const publicDidDocument = did.publicDidDocument
      const didId = publicDidDocument.id
      const body = { type, url, didId, username, password }
      const chluApiClient = await getChluAPIClient()
      const dagNode = await createDAGNode(Buffer.from(JSON.stringify(body)))
      const signature = await chluApiClient.didIpfsHelper.signMultihash(dagNode.toJSON().multihash, did)

      const response = await fetch(`${API_URL}/api/v1/crawl`, {
        method: 'POST',
        body: JSON.stringify({ ...body, publicDidDocument, signature }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      })

      const responseJson = await response.json()

      console.log('Crawlers running:')
      console.log(responseJson)
    } catch (error) {
      console.log(error)
    }
  }
}

export function importReviews() {
  return async (dispatch, getState) => {
    //TODO: check logged in, and that crawler is not already running
    const values = getFormValues('import-reviews')(getState())
    const servicesList = ['yelp', 'tripadvisor', 'fiverr', 'linkedin', 'upwork']
    let count = 0
    for (const service of servicesList) {
      if (get(values, `${service}-user`) || get(values, `${service}-url`)) {
        count = count + 1
        await dispatch(startCrawler(service,
          get(values, `${service}-url`),
          get(values, `${service}-user`),
          get(values, `${service}-password`)
        ))
      }
    }
    // TODO: The form fields should be cleared here
    return count
  }
}

export function pollCrawlerProgress() {
  return async (dispatch, getState) => {
    const state = getState()
    const didId = get(state, 'data.wallet.did.publicDidDocument.id', null)

    if (!didId) {
      console.error("Cannot read crawler progress without DID ID.")
      return
    }

    let isBackendCrawlingInProgress = false
    let wasBackendCrawlingInProgress = false

    do {
      isBackendCrawlingInProgress = await readCrawlerProgress(didId)

      console.log('crawler running state: ' + isBackendCrawlingInProgress)

      if (isBackendCrawlingInProgress) {
        dispatch(startCrawlerAction())

        // Wait a bit before polling again.
        await new Promise(resolve => window.setTimeout(resolve, CRAWLER_PROGRESS_POLL_INTERVAL))
      } else {
        if (wasBackendCrawlingInProgress) {
          // If the crawler was running on the backend before, we should assume new reviews are available now.
          readMyReputation()(dispatch, getState)
        }

        dispatch(finishCrawler())
      }

      wasBackendCrawlingInProgress = isBackendCrawlingInProgress
    } while (isBackendCrawlingInProgress)
  }
}

async function readCrawlerProgress(didId) {
  try {
    const response = await fetch(`${API_URL}/api/v1/crawl/${didId}`, {
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    })

    const responseJson = await response.json()
    console.log(responseJson)
    return responseJson.status === 'RUNNING'
  } catch (err) {
    console.error(err)
    return false
  }
}

export default handleActions({
  [CRAWLER_START]: state => ({
    ...state,
    running: true,
    error: null
  }),
  [CRAWLER_ERROR]: (state, { payload: error }) => ({
    ...state,
    running: false,
    error
  }),
  [CRAWLER_FINISH]: state => ({
    ...state,
    running: false,
    error: null
  })
}, getInitialState())
<commit_msg>use newest api publish API<commit_after>import { createAction, handleActions } from 'redux-actions'
import { readMyReputation } from './reputation'
// Helpers
import { get } from 'lodash'
import { getChluAPIClient } from 'helpers/chlu'
import { createDAGNode } from 'chlu-api-client/src/utils/ipfs'
import { getFormValues } from 'redux-form'

// Constants
const CRAWLER_START = 'crawler/START'
const CRAWLER_ERROR = 'crawler/ERROR'
const CRAWLER_FINISH = 'crawler/FINISH'

const API_URL = process.env.REACT_APP_CHLU_PUBLISH_URL || 'https://publish.chlu.io'
const CRAWLER_PROGRESS_POLL_INTERVAL = 5000 // ms

function getInitialState() {
  return {
    running: false,
    error: null
  }
}

export const crawlerError = createAction(CRAWLER_ERROR)
const startCrawlerAction = createAction(CRAWLER_START)
export const finishCrawler = createAction(CRAWLER_FINISH)

export function startCrawler(type, url, username, password) {
  return async (dispatch, getState) => {
    const state = getState()
    try {
      const signedInDid = get(state, 'data.wallet.did', null)
      const signedOutDid = get(state, 'components.createWallet.walletCreated.did', null)
      const did = signedInDid || signedOutDid
      const publicDidDocument = did.publicDidDocument
      const didId = publicDidDocument.id
      const body = { type, url, didId, username, password }
      const chluApiClient = await getChluAPIClient()
      const dagNode = await createDAGNode(Buffer.from(JSON.stringify(body)))
      const signature = await chluApiClient.didIpfsHelper.signMultihash(dagNode.toJSON().multihash, did)

      const response = await fetch(`${API_URL}/api/v1/crawl`, {
        method: 'POST',
        body: JSON.stringify({ ...body, publicDidDocument, signature }),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      })

      const responseJson = await response.json()

      console.log('Crawlers running:')
      console.log(responseJson)
    } catch (error) {
      console.log(error)
    }
  }
}

export function importReviews() {
  return async (dispatch, getState) => {
    //TODO: check logged in, and that crawler is not already running
    const values = getFormValues('import-reviews')(getState())
    const servicesList = ['yelp', 'tripadvisor', 'fiverr', 'linkedin', 'upwork']
    let count = 0
    for (const service of servicesList) {
      if (get(values, `${service}-user`) || get(values, `${service}-url`)) {
        count = count + 1
        await dispatch(startCrawler(service,
          get(values, `${service}-url`),
          get(values, `${service}-user`),
          get(values, `${service}-password`)
        ))
      }
    }
    // TODO: The form fields should be cleared here
    return count
  }
}

export function pollCrawlerProgress() {
  return async (dispatch, getState) => {
    const state = getState()
    const didId = get(state, 'data.wallet.did.publicDidDocument.id', null)

    if (!didId) {
      console.error("Cannot read crawler progress without DID ID.")
      return
    }

    let isBackendCrawlingInProgress = false
    let wasBackendCrawlingInProgress = false

    do {
      isBackendCrawlingInProgress = hasInProgressJobs(await getCrawlerStatus(didId))

      console.log('crawler running state: ' + isBackendCrawlingInProgress)

      if (isBackendCrawlingInProgress) {
        dispatch(startCrawlerAction())

        // Wait a bit before polling again.
        await new Promise(resolve => window.setTimeout(resolve, CRAWLER_PROGRESS_POLL_INTERVAL))
      } else {
        if (wasBackendCrawlingInProgress) {
          // If the crawler was running on the backend before, we should assume new reviews are available now.
          readMyReputation()(dispatch, getState)
        }

        dispatch(finishCrawler())
      }

      wasBackendCrawlingInProgress = isBackendCrawlingInProgress
    } while (isBackendCrawlingInProgress)
  }
}

function hasInProgressJobs(crawlerStatus) {
  for (const status of crawlerStatus.rows) {
    if (status.status === 'CREATED' || status.status === 'RUNNING') return true
  }
  return false
}

async function getCrawlerStatus(didId) {
  try {
    const response = await fetch(`${API_URL}/api/v1/crawl/${didId}`, {
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    })

    const responseJson = await response.json()
    console.log(responseJson)
    return responseJson
  } catch (err) {
    console.error(err)
    return []
  }
}

export default handleActions({
  [CRAWLER_START]: state => ({
    ...state,
    running: true,
    error: null
  }),
  [CRAWLER_ERROR]: (state, { payload: error }) => ({
    ...state,
    running: false,
    error
  }),
  [CRAWLER_FINISH]: state => ({
    ...state,
    running: false,
    error: null
  })
}, getInitialState())
<|endoftext|>


<commit_before>Object.defineProperty(Array.prototype, 'fill', {
	configurable: true,
	value: function fill(value) {
		if (this === undefined || this === null) {
			throw new TypeError(this + ' is not an object');
		}

		var arrayLike = this instanceof String ? this.split('') : Object(this);

		var length = Math.max(Math.min(arrayLike.length, 9007199254740991), 0) || 0;

		var relativeStart = 1 in arguments ? Math.floor(Number(arguments[1])) || 0 : 0;

		relativeStart = relativeStart < 0 ? Math.max(length + relativeStart, 0) : Math.min(relativeStart, length);

		var relativeEnd = 2 in arguments ? Math.floor(Number(arguments[2])) || 0 : length;

		relativeEnd = relativeEnd < 0 ? Math.max(length + arguments[2], 0) : Math.min(relativeEnd, length);

		while (relativeStart < relativeEnd) {
			arrayLike[relativeStart] = value;

			++relativeStart;
		}

		return arrayLike;
	},
	writable: true
});
<commit_msg>if end is undefined use length of `this`<commit_after>Object.defineProperty(Array.prototype, 'fill', {
	configurable: true,
	value: function fill(value) {
		if (this === undefined || this === null) {
			throw new TypeError(this + ' is not an object');
		}

		var arrayLike = this instanceof String ? this.split('') : Object(this);

		var length = Math.max(Math.min(arrayLike.length, 9007199254740991), 0) || 0;

		var relativeStart = 1 in arguments ? Math.floor(Number(arguments[1])) || 0 : 0;

		relativeStart = relativeStart < 0 ? Math.max(length + relativeStart, 0) : Math.min(relativeStart, length);

		var relativeEnd = 2 in arguments && arguments[2] !== undefined ? Math.floor(Number(arguments[2])) || 0 : length;

		relativeEnd = relativeEnd < 0 ? Math.max(length + arguments[2], 0) : Math.min(relativeEnd, length);

		while (relativeStart < relativeEnd) {
			arrayLike[relativeStart] = value;

			++relativeStart;
		}

		return arrayLike;
	},
	writable: true
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name angular-uploadcare.directive:Uploadcare
 * @description Provides a directive for the Uploadcare widget.
 * # Uploadcare
 */
angular.module('ng-uploadcare', [])
  .directive('uploadcareWidget', function () {
    return {
      restrict: 'A',
      require: 'ngModel',
      scope: {
        onWidgetReady: '&',
        onUploadComplete: '&',
        onChange: '&',
        ngModel: '='
      },
      controller: ['$scope', '$element', '$attrs', '$log', function($scope, $element, $attrs, $log) {
        if(!uploadcare) {
          $log.error('Uploadcare script has not been loaded!.');
          return;
        }
        $element.attr('type', 'hidden');
        $scope.widget = uploadcare.Widget($element);
        $scope.onWidgetReady({widget: $scope.widget});
        $scope.widget.onUploadComplete(function(info) {
          $scope.onUploadComplete({info: info});
        });
        $scope.widget.onChange(function(data) {
          // add data binding for hidden inputs
          if (data) {
            data.promise().done(function(info) {
              $scope.$apply(function () {
                $scope.ngModel = $element.val()
              });
            });
          }
          $scope.onChange({data: data});
        })
      }]
    };
  });
<commit_msg>Add the module wrapper<commit_after>(function(root, factory) {
  'use strict';
  if (typeof define === 'function' && define.amd) {
    define(['angular'], factory);
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory(require('angular'));
  } else {
    factory(root.angular);
  }
}(this, function(angular) {
  'use strict';

  /**
   * @ngdoc directive
   * @name angular-uploadcare.directive:Uploadcare
   * @description Provides a directive for the Uploadcare widget.
   * # Uploadcare
   */
  angular.module('ng-uploadcare', [])
    .directive('uploadcareWidget', function() {
      return {
        restrict: 'A',
        require: 'ngModel',
        scope: {
          onWidgetReady: '&',
          onUploadComplete: '&',
          onChange: '&',
          ngModel: '='
        },
        controller: ['$scope', '$element', '$attrs', '$log', function($scope, $element, $attrs, $log) {
          if (!uploadcare) {
            $log.error('Uploadcare script has not been loaded!.');
            return;
          }
          $element.attr('type', 'hidden');
          $scope.widget = uploadcare.Widget($element);
          $scope.onWidgetReady({widget: $scope.widget});
          $scope.widget.onUploadComplete(function(info) {
            $scope.onUploadComplete({info: info});
          });
          $scope.widget.onChange(function(data) {
            // add data binding for hidden inputs
            if (data) {
              data.promise().done(function(info) {
                $scope.$apply(function() {
                  $scope.ngModel = $element.val()
                });
              });
            }
            $scope.onChange({data: data});
          })
        }]
      };
    });
}));
<|endoftext|>


<commit_before>/*!
 * Copyright (C) 2017 Glayzzle (BSD3 License)
 * @authors https://github.com/glayzzle/php-reflection/graphs/contributors
 * @url http://glayzzle.com
 */
'use strict';

var block = require('./block');
var expr = require('./expr');

/**
 * ** Extends from {@link BLOCK.md|:link: block} **
 *
 * The declare construct is used to set execution directives
 * for a block of code.
 *
 * {@link http://php.net/manual/en/control-structures.declare.php}
 *
 * @public
 * @constructor Declare
 * @property {Object} options List of key/value declarations
 */
var Declare = block.extends('declare');

/**
 * @protected reads each declared option
 */
Declare.prototype.consume = function(ast) {

  // @fixme object are not exported as cache
  this.options = {};
  for(var k in ast.what) {
    this.options[k] = expr.resolve(this, ast.what[k]);
  }

  // Iterator over each namespace item
  this.scanForChilds(ast.children);
};

module.exports = Declare;
<commit_msg>migrate declare node with new api<commit_after>/*!
 * Copyright (C) 2017 Glayzzle (BSD3 License)
 * @authors https://github.com/glayzzle/php-reflection/graphs/contributors
 * @url http://glayzzle.com
 */
'use strict';

var Block = require('./block');
var Expr = require('./expr');

/**
 * ** Extends from {@link BLOCK.md|:link: block} **
 *
 * The declare construct is used to set execution directives
 * for a block of code.
 *
 * {@link http://php.net/manual/en/control-structures.declare.php}
 *
 * @public
 * @constructor Declare
 * @property {Object} options List of key/value declarations
 */
var Declare = Block.extends('declare');

/**
 * @protected reads each declared option
 */
Declare.prototype.consume = function(file, parent, ast) {
    this.options = {};
    for(var k in ast.what) {
        this.options[k] = Expr.resolve(this, ast.what[k]);
    }
    Block.prototype.consume.apply(this, arguments);
};

module.exports = Declare;
<|endoftext|>


<commit_before>/**
 * @file
 * Replaces broken images with dummy image.
 *
 * @author Mike Shiyan
 */

// ==UserScript==
// @name Broken Image Replacer
// ==/UserScript==

(function () {

  'use strict';

  if (typeof jQuery == 'undefined') {
    // Only sites having jQuery are supported currently.
    return;
  }

  var $ = jQuery;

  $(document).ready(function($) {
    // Change image sources without waiting for images attempting to load.
    if (typeof localStorage != 'undefined') {
      var data = JSON.parse(localStorage.getItem('broken_image_replacer')) || {};

      $.each(data, function (source, dimensions) {
        $('img[src="' + source + '"]').attr('src', 'http://dummyimage.com/' + dimensions[0] + 'x' + dimensions[1] + '/cccccc/ffffff.png');
      });
    }
  });

  var cache = {};

  $('img').on('error', function () {
    var $image = $(this);
    var source = $image.attr('src');

    if (source.indexOf('http://dummyimage.com/') == -1) {
      var w = $image.width();
      var h = $image.height();

      $image.attr('src', 'http://dummyimage.com/' + w + 'x' + h + '/cccccc/ffffff.png');
      cache[source] = [w, h];
    }
  });

  $(window).on('load', function() {
    if (typeof localStorage != 'undefined') {
      var data = JSON.parse(localStorage.getItem('broken_image_replacer')) || {};
      $.extend(data, cache);
      localStorage.setItem('broken_image_replacer', JSON.stringify(data));
    }
  });

})();
<commit_msg>Replaced dummyimage service with custom CSS implementation.<commit_after>/**
 * @file
 * Replaces broken images with dummy image.
 *
 * @author Mike Shiyan
 */

// ==UserScript==
// @name Broken Image Replacer
// ==/UserScript==

(function () {

  'use strict';

  function fix (image) {
    var width  = image.width;
    var height = image.height;
    var text   = width + ' × ' + height;

    // Calculate the font size. The final value cannot be less than 5px or more
    // than a 3-quarters of image height.
    var fontSize = Math.max(Math.min(width / text.length * 1.5, height * 0.75), 5);

    image.alt = text;
    // Trick the browser to show the just added 'alt' attribute.
    image.src = null;
    image.removeAttribute('src');

    // Apply CSS.
    image.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAE0lEQVR42mNgAIIzZ878hxBAAAA8bwbHJ1VugQAAAABJRU5ErkJggg==")';
    image.style.fontFamily = 'monospace';
    image.style.fontSize = fontSize + 'px';
    image.style.lineHeight = (height - 4) + 'px';
    image.style.textAlign = 'center';

    image.classList.add('broken-image');
  }

  var cache = {};

  var images = document.getElementsByTagName('img');
  for (var i = 0, total = images.length; i < total; i++) {
    images[i].onerror = function () {
      // Check that image is not processed yet.
      if (!this.classList.contains('broken-image')) {
        cache[this.src] = 1;
        fix(this);
      }

      this.onerror = null;
    };
  }

  if (typeof jQuery != 'undefined') {
    // Currently only sites having jQuery are supported for certain operations.
    var $ = jQuery;

    if (typeof localStorage != 'undefined') {
      $(document).ready(function ($) {
        // Change image sources without waiting for images attempting to load.
        var data = JSON.parse(localStorage.getItem('broken_image_replacer')) || {};

        $('img[src]').each(function () {
          if (data[$(this).attr('src')]) {
            fix(this);
          }
        });
      });

      var updateStorage = function () {
        var data = JSON.parse(localStorage.getItem('broken_image_replacer')) || {};
        $.extend(data, cache);
        localStorage.setItem('broken_image_replacer', JSON.stringify(data));
      };

      if (typeof $(window).on == 'function') {
        $(window).on('load', updateStorage);
      }
      else {
        $(window).load(updateStorage);
      }
    }

    $(window).resize(function () {
      $('img.broken-image').each(function () {
        fix(this);
      });
    });
  }

})();
<|endoftext|>


<commit_before>/*
 *  Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
var pageDecorators = {};
(function(pageDecorators) {
    var log = new Log();
    pageDecorators.assetCategoryDetails = function(ctx, page, utils) {
        page.assetCategoryDetails = {};
        page.assetCategoryDetails.hasCategories = false;
        page.assetCategoryDetails.values = [];
        var categoryField = ctx.rxtManager.getCategoryField(ctx.assetType);
        var categoryValues = [];
        var field = ctx.rxtManager.getRxtField(ctx.assetType, categoryField);
        if (!field) {
            return;
        }
        var values = field.values[0].value;
        if (!values) {
            return;
        }
        for (var index = 0; index < values.length; index++) {
            categoryValues.push(values[index].value);
        }
        page.assetCategoryDetails.hasCategories = true;
        page.assetCategoryDetails.values = categoryValues;
    };
    pageDecorators.populateAttachedLifecycles = function(ctx, page, utils) {
        var am = assetManager(ctx);
        //Check if an asset exists
        if ((page.assets) && (page.assets.id)) {
            var lifecycles = am.listAllAttachedLifecycles(page.assets.id);
            var lifecycle;
            var modifiedLifecycles = [];
            var entry ;
            for (var index = 0; index < lifecycles.length; index++) {
                lifecycle = lifecycles[index];
                entry = {};
                entry.active = false;
                entry.name = lifecycle;
                if(page.assets.lifecycle ===  lifecycle){
                    entry.active = true;
                }
                modifiedLifecycles.push(entry);
            }
            modifiedLifecycles.push({"active":false,"name":"SampleLifeCycle2"});
            modifiedLifecycles.push({"active":false,"name":"MobileAppLifeCycle"});
            modifiedLifecycles.push({"active":false,"name":"MockLifecycle"});
            page.assets.availableLifecycles = modifiedLifecycles;
            page.assets.hasMultipleLifecycles = true;//(lifecycles.length > 1) ? true : false;
        }
    };
    var assetManager = function(ctx) {
        var rxt = require('rxt');
        var type = ctx.assetType;
        var am = rxt.asset.createUserAssetManager(ctx.session, type);
        return am;
    };
}(pageDecorators));<commit_msg>Removed the hard coded lifecycle count<commit_after>/*
 *  Copyright (c) 2005-2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
var pageDecorators = {};
(function(pageDecorators) {
    var log = new Log();
    pageDecorators.assetCategoryDetails = function(ctx, page, utils) {
        page.assetCategoryDetails = {};
        page.assetCategoryDetails.hasCategories = false;
        page.assetCategoryDetails.values = [];
        var categoryField = ctx.rxtManager.getCategoryField(ctx.assetType);
        var categoryValues = [];
        var field = ctx.rxtManager.getRxtField(ctx.assetType, categoryField);
        if (!field) {
            return;
        }
        var values = field.values[0].value;
        if (!values) {
            return;
        }
        for (var index = 0; index < values.length; index++) {
            categoryValues.push(values[index].value);
        }
        page.assetCategoryDetails.hasCategories = true;
        page.assetCategoryDetails.values = categoryValues;
    };
    pageDecorators.populateAttachedLifecycles = function(ctx, page, utils) {
        var am = assetManager(ctx);
        //Check if an asset exists
        if ((page.assets) && (page.assets.id)) {
            var lifecycles = am.listAllAttachedLifecycles(page.assets.id);
            var lifecycle;
            var modifiedLifecycles = [];
            var entry ;
            for (var index = 0; index < lifecycles.length; index++) {
                lifecycle = lifecycles[index];
                entry = {};
                entry.active = false;
                entry.name = lifecycle;
                if(page.assets.lifecycle ===  lifecycle){
                    entry.active = true;
                }
                modifiedLifecycles.push(entry);
            }
            //modifiedLifecycles.push({"active":false,"name":"SampleLifeCycle2"});
            //modifiedLifecycles.push({"active":false,"name":"MobileAppLifeCycle"});
            //modifiedLifecycles.push({"active":false,"name":"MockLifecycle"});
            page.assets.availableLifecycles = modifiedLifecycles;
            page.assets.hasMultipleLifecycles = (lifecycles.length > 1) ? true : false;
        }
    };
    var assetManager = function(ctx) {
        var rxt = require('rxt');
        var type = ctx.assetType;
        var am = rxt.asset.createUserAssetManager(ctx.session, type);
        return am;
    };
}(pageDecorators));<|endoftext|>


<commit_before>import { extend } from 'underscore';

const Backbone = require('backbone');
const Properties = require('./Properties');
const PropertyFactory = require('./PropertyFactory');

module.exports = Backbone.Model.extend({
  defaults: {
    id: '',
    name: '',
    open: true,
    buildProps: '',
    extendBuilded: 1,
    properties: []
  },

  initialize(opts) {
    const o = opts || {};
    const builded = this.buildProperties(o.buildProps);
    const name = this.get('name') || '';
    let props = [];
    !this.get('id') && this.set('id', name.replace(/ /g, '_').toLowerCase());

    if (!builded) props = this.get('properties');
    else props = this.extendProperties(builded);

    const propsModel = new Properties(props);
    propsModel.sector = this;
    this.set('properties', propsModel);
  },

  /**
   * Extend properties
   * @param {Array<Object>} props Start properties
   * @param {Array<Object>} moProps Model props
   * @param {Boolean} ex Returns the same amount of passed model props
   * @return {Array<Object>} Final props
   * @private
   */
  extendProperties(props, moProps, ex) {
    var pLen = props.length;
    var mProps = moProps || this.get('properties');
    var ext = this.get('extendBuilded');
    var isolated = [];

    for (var i = 0, len = mProps.length; i < len; i++) {
      var mProp = mProps[i];
      var found = 0;

      for (var j = 0; j < pLen; j++) {
        var prop = props[j];
        if (mProp.property == prop.property || mProp.id == prop.property) {
          // Check for nested properties
          var mPProps = mProp.properties;
          if (mPProps && mPProps.length) {
            mProp.properties = this.extendProperties(
              prop.properties || [],
              mPProps,
              1
            );
          }
          props[j] = ext ? extend(prop, mProp) : mProp;
          isolated[j] = props[j];
          found = 1;
          continue;
        }
      }

      if (!found) {
        props.push(mProp);
        isolated.push(mProp);
      }
    }

    return ex ? isolated : props;
  },

  /**
   * Build properties
   * @param {Array<string>} propr Array of props as sting
   * @return {Array<Object>}
   * @private
   */
  buildProperties(props) {
    var r;
    var buildP = props || [];

    if (!buildP.length) return;

    if (!this.propFactory) this.propFactory = new PropertyFactory();

    r = this.propFactory.build(buildP);

    return r;
  }
});
<commit_msg>Fix extendProperties in Sector. Closes #1913<commit_after>import { extend } from 'underscore';

const Backbone = require('backbone');
const Properties = require('./Properties');
const PropertyFactory = require('./PropertyFactory');

module.exports = Backbone.Model.extend({
  defaults: {
    id: '',
    name: '',
    open: true,
    buildProps: '',
    extendBuilded: 1,
    properties: []
  },

  initialize(opts) {
    const o = opts || {};
    const builded = this.buildProperties(o.buildProps);
    const name = this.get('name') || '';
    let props = [];
    !this.get('id') && this.set('id', name.replace(/ /g, '_').toLowerCase());

    if (!builded) props = this.get('properties');
    else props = this.extendProperties(builded);

    const propsModel = new Properties(props);
    propsModel.sector = this;
    this.set('properties', propsModel);
  },

  /**
   * Extend properties
   * @param {Array<Object>} props Start properties
   * @param {Array<Object>} moProps Model props
   * @param {Boolean} ex Returns the same amount of passed model props
   * @return {Array<Object>} Final props
   * @private
   */
  extendProperties(props, moProps, ex) {
    var pLen = props.length;
    var mProps = moProps || this.get('properties');
    var ext = this.get('extendBuilded');
    var isolated = [];

    for (var i = 0, len = mProps.length; i < len; i++) {
      var mProp = mProps[i];
      var found = 0;

      for (var j = 0; j < pLen; j++) {
        var prop = props[j];
        if (mProp.property == prop.property || mProp.id == prop.property) {
          // Check for nested properties
          var mPProps = mProp.properties;
          if (mPProps && mPProps.length) {
            mProp.properties = this.extendProperties(
              prop.properties || [],
              mPProps,
              1
            );
          }
          props[j] = ext ? extend(prop, mProp) : mProp;
          isolated[j] = props[j];
          found = 1;
          continue;
        }
      }

      if (!found) {
        props.push(mProp);
        isolated.push(mProp);
      }
    }

    return ex ? isolated.filter(i => i) : props;
  },

  /**
   * Build properties
   * @param {Array<string>} propr Array of props as sting
   * @return {Array<Object>}
   * @private
   */
  buildProperties(props) {
    var r;
    var buildP = props || [];

    if (!buildP.length) return;

    if (!this.propFactory) this.propFactory = new PropertyFactory();

    r = this.propFactory.build(buildP);

    return r;
  }
});
<|endoftext|>


<commit_before>const jwt = require('jsonwebtoken');
const url = require('url');
const models = require('../../../models');
const errors = require('@tryghost/errors');
const {i18n} = require('../../../lib/common');
const _ = require('lodash');

let JWT_OPTIONS_DEFAULTS = {
    algorithms: ['HS256'],
    maxAge: '5m'
};

/**
 * Remove 'Ghost' from raw authorization header and extract the JWT token.
 * Eg. Authorization: Ghost ${JWT}
 * @param {string} header
 */
const _extractTokenFromHeader = function extractTokenFromHeader(header) {
    const [scheme, token] = header.split(' ');

    if (/^Ghost$/i.test(scheme)) {
        return token;
    }
};

/**
 * Extract JWT token from admin API URL query
 * Eg. ${ADMIN_API_URL}/?token=${JWT}
 * @param {string} reqUrl
 */
const _extractTokenFromUrl = function extractTokenFromUrl(reqUrl) {
    const {query} = url.parse(reqUrl, true);
    return query.token;
};

const authenticate = (req, res, next) => {
    // CASE: we don't have an Authorization header so allow fallthrough to other
    // auth middleware or final "ensure authenticated" check
    if (!req.headers || !req.headers.authorization) {
        req.api_key = null;
        return next();
    }
    const token = _extractTokenFromHeader(req.headers.authorization);

    if (!token) {
        return next(new errors.UnauthorizedError({
            message: i18n.t('errors.middleware.auth.incorrectAuthHeaderFormat'),
            code: 'INVALID_AUTH_HEADER'
        }));
    }

    return authenticateWithToken(req, res, next, {token, JWT_OPTIONS: JWT_OPTIONS_DEFAULTS});
};

const authenticateWithUrl = (req, res, next) => {
    const token = _extractTokenFromUrl(req.originalUrl);
    if (!token) {
        return next(new errors.UnauthorizedError({
            message: i18n.t('errors.middleware.auth.invalidTokenWithMessage', {message: 'No token found in URL'}),
            code: 'INVALID_JWT'
        }));
    }
    // CASE: Scheduler publish URLs can have long maxAge but controllerd by expiry and neverBefore
    return authenticateWithToken(req, res, next, {token, JWT_OPTIONS: _.omit(JWT_OPTIONS_DEFAULTS, 'maxAge')});
};

/**
 * Admin API key authentication flow:
 * 1. extract the JWT token from the `Authorization: Ghost xxxx` header or from URL(for schedules)
 * 2. decode the JWT to extract the api_key id from the "key id" header claim
 * 3. find a matching api_key record
 * 4. verify the JWT (matching secret, matching URL path, not expired)
 * 5. place the api_key object on `req.api_key`
 *
 * There are some specifcs of the JWT that we expect:
 * - the "Key ID" header parameter should be set to the id of the api_key used to sign the token
 *   https://tools.ietf.org/html/rfc7515#section-4.1.4
 * - the "Audience" claim should match the requested API path
 *   https://tools.ietf.org/html/rfc7519#section-4.1.3
 */
const authenticateWithToken = (req, res, next, {token, JWT_OPTIONS}) => {
    const decoded = jwt.decode(token, {complete: true});

    if (!decoded || !decoded.header) {
        return next(new errors.BadRequestError({
            message: i18n.t('errors.middleware.auth.invalidToken'),
            code: 'INVALID_JWT'
        }));
    }

    const apiKeyId = decoded.header.kid;

    if (!apiKeyId) {
        return next(new errors.BadRequestError({
            message: i18n.t('errors.middleware.auth.adminApiKidMissing'),
            code: 'MISSING_ADMIN_API_KID'
        }));
    }

    models.ApiKey.findOne({id: apiKeyId}).then((apiKey) => {
        if (!apiKey) {
            return next(new errors.UnauthorizedError({
                message: i18n.t('errors.middleware.auth.unknownAdminApiKey'),
                code: 'UNKNOWN_ADMIN_API_KEY'
            }));
        }

        if (apiKey.get('type') !== 'admin') {
            return next(new errors.UnauthorizedError({
                message: i18n.t('errors.middleware.auth.invalidApiKeyType'),
                code: 'INVALID_API_KEY_TYPE'
            }));
        }

        // Decoding from hex and transforming into bytes is here to
        // keep comparison of the bytes that are stored in the secret.
        // Useful context:
        // https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138
        const secret = Buffer.from(apiKey.get('secret'), 'hex');

        const {pathname} = url.parse(req.originalUrl);
        const [hasMatch, version = 'v2', api = 'admin'] = pathname.match(/ghost\/api\/([^/]+)\/([^/]+)\/(.+)*/); // eslint-disable-line no-unused-vars

        // ensure the token was meant for this api version
        const options = Object.assign({
            audience: new RegExp(`\/?${version}\/${api}\/?$`) // eslint-disable-line no-useless-escape
        }, JWT_OPTIONS);

        try {
            jwt.verify(token, secret, options);
        } catch (err) {
            if (err.name === 'TokenExpiredError' || err.name === 'JsonWebTokenError') {
                return next(new errors.UnauthorizedError({
                    message: i18n.t('errors.middleware.auth.invalidTokenWithMessage', {message: err.message}),
                    code: 'INVALID_JWT',
                    err
                }));
            }

            // unknown error
            return next(new errors.InternalServerError({err}));
        }

        // authenticated OK

        if (apiKey.get('user_id')) {
            // fetch the user and store it on the request for later checks and logging
            models.User.findOne({id: apiKey.get('user_id')}).then((user) => {
                req.user = user;
                next();
            });
            return;
        }

        // store the api key on the request for later checks and logging
        req.api_key = apiKey;
        next();
    }).catch((err) => {
        next(new errors.InternalServerError({err}));
    });
};

module.exports = {
    authenticate,
    authenticateWithUrl
};
<commit_msg>Disabled Personal Tokens of inactive/locked users<commit_after>const jwt = require('jsonwebtoken');
const url = require('url');
const models = require('../../../models');
const errors = require('@tryghost/errors');
const {i18n} = require('../../../lib/common');
const _ = require('lodash');

let JWT_OPTIONS_DEFAULTS = {
    algorithms: ['HS256'],
    maxAge: '5m'
};

/**
 * Remove 'Ghost' from raw authorization header and extract the JWT token.
 * Eg. Authorization: Ghost ${JWT}
 * @param {string} header
 */
const _extractTokenFromHeader = function extractTokenFromHeader(header) {
    const [scheme, token] = header.split(' ');

    if (/^Ghost$/i.test(scheme)) {
        return token;
    }
};

/**
 * Extract JWT token from admin API URL query
 * Eg. ${ADMIN_API_URL}/?token=${JWT}
 * @param {string} reqUrl
 */
const _extractTokenFromUrl = function extractTokenFromUrl(reqUrl) {
    const {query} = url.parse(reqUrl, true);
    return query.token;
};

const authenticate = (req, res, next) => {
    // CASE: we don't have an Authorization header so allow fallthrough to other
    // auth middleware or final "ensure authenticated" check
    if (!req.headers || !req.headers.authorization) {
        req.api_key = null;
        return next();
    }
    const token = _extractTokenFromHeader(req.headers.authorization);

    if (!token) {
        return next(new errors.UnauthorizedError({
            message: i18n.t('errors.middleware.auth.incorrectAuthHeaderFormat'),
            code: 'INVALID_AUTH_HEADER'
        }));
    }

    return authenticateWithToken(req, res, next, {token, JWT_OPTIONS: JWT_OPTIONS_DEFAULTS});
};

const authenticateWithUrl = (req, res, next) => {
    const token = _extractTokenFromUrl(req.originalUrl);
    if (!token) {
        return next(new errors.UnauthorizedError({
            message: i18n.t('errors.middleware.auth.invalidTokenWithMessage', {message: 'No token found in URL'}),
            code: 'INVALID_JWT'
        }));
    }
    // CASE: Scheduler publish URLs can have long maxAge but controllerd by expiry and neverBefore
    return authenticateWithToken(req, res, next, {token, JWT_OPTIONS: _.omit(JWT_OPTIONS_DEFAULTS, 'maxAge')});
};

/**
 * Admin API key authentication flow:
 * 1. extract the JWT token from the `Authorization: Ghost xxxx` header or from URL(for schedules)
 * 2. decode the JWT to extract the api_key id from the "key id" header claim
 * 3. find a matching api_key record
 * 4. verify the JWT (matching secret, matching URL path, not expired)
 * 5. place the api_key object on `req.api_key`
 *
 * There are some specifcs of the JWT that we expect:
 * - the "Key ID" header parameter should be set to the id of the api_key used to sign the token
 *   https://tools.ietf.org/html/rfc7515#section-4.1.4
 * - the "Audience" claim should match the requested API path
 *   https://tools.ietf.org/html/rfc7519#section-4.1.3
 */
const authenticateWithToken = (req, res, next, {token, JWT_OPTIONS}) => {
    const decoded = jwt.decode(token, {complete: true});

    if (!decoded || !decoded.header) {
        return next(new errors.BadRequestError({
            message: i18n.t('errors.middleware.auth.invalidToken'),
            code: 'INVALID_JWT'
        }));
    }

    const apiKeyId = decoded.header.kid;

    if (!apiKeyId) {
        return next(new errors.BadRequestError({
            message: i18n.t('errors.middleware.auth.adminApiKidMissing'),
            code: 'MISSING_ADMIN_API_KID'
        }));
    }

    models.ApiKey.findOne({id: apiKeyId}).then((apiKey) => {
        if (!apiKey) {
            return next(new errors.UnauthorizedError({
                message: i18n.t('errors.middleware.auth.unknownAdminApiKey'),
                code: 'UNKNOWN_ADMIN_API_KEY'
            }));
        }

        if (apiKey.get('type') !== 'admin') {
            return next(new errors.UnauthorizedError({
                message: i18n.t('errors.middleware.auth.invalidApiKeyType'),
                code: 'INVALID_API_KEY_TYPE'
            }));
        }

        // Decoding from hex and transforming into bytes is here to
        // keep comparison of the bytes that are stored in the secret.
        // Useful context:
        // https://github.com/auth0/node-jsonwebtoken/issues/208#issuecomment-231861138
        const secret = Buffer.from(apiKey.get('secret'), 'hex');

        const {pathname} = url.parse(req.originalUrl);
        const [hasMatch, version = 'v2', api = 'admin'] = pathname.match(/ghost\/api\/([^/]+)\/([^/]+)\/(.+)*/); // eslint-disable-line no-unused-vars

        // ensure the token was meant for this api version
        const options = Object.assign({
            audience: new RegExp(`\/?${version}\/${api}\/?$`) // eslint-disable-line no-useless-escape
        }, JWT_OPTIONS);

        try {
            jwt.verify(token, secret, options);
        } catch (err) {
            if (err.name === 'TokenExpiredError' || err.name === 'JsonWebTokenError') {
                return next(new errors.UnauthorizedError({
                    message: i18n.t('errors.middleware.auth.invalidTokenWithMessage', {message: err.message}),
                    code: 'INVALID_JWT',
                    err
                }));
            }

            // unknown error
            return next(new errors.InternalServerError({err}));
        }

        // authenticated OK

        if (apiKey.get('user_id')) {
            // fetch the user and store it on the request for later checks and logging
            return models.User.findOne(
                {id: apiKey.get('user_id'), status: 'active'},
                {require: true}
            ).then((user) => {
                req.user = user;
                next();
            });
        }

        // store the api key on the request for later checks and logging
        req.api_key = apiKey;
        next();
    }).catch((err) => {
        next(new errors.InternalServerError({err}));
    });
};

module.exports = {
    authenticate,
    authenticateWithUrl
};
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');
var net = require('net');

var TChannelConnection = require('./connection');
var errors = require('./errors');
var Request = require('./request');
var PreferOutgoingHandler = require('./peer_score_strategies.js').PreferOutgoingHandler;

var DEFAULT_REPORT_INTERVAL = 1000;

function TChannelPeer(channel, hostPort, options) {
    if (!(this instanceof TChannelPeer)) {
        return new TChannelPeer(channel, hostPort, options);
    }
    var self = this;
    EventEmitter.call(self);

    self.stateChangedEvent = self.defineEvent('stateChanged');
    self.allocConnectionEvent = self.defineEvent('allocConnection');

    assert(hostPort !== '0.0.0.0:0', 'Cannot create ephemeral peer');

    self.channel = channel;
    self.logger = self.channel.logger;
    self.timers = self.channel.timers;
    self.random = self.channel.random;
    self.options = options || {};
    self.hostPort = hostPort;
    self.connections = [];
    self.pendingIdentified = 0;
    self.heapElements = [];
    self.handler = new PreferOutgoingHandler(self);

    self.reportInterval = self.options.reportInterval || DEFAULT_REPORT_INTERVAL;
    if (self.reportInterval > 0 && self.channel.emitConnectionMetrics) {
        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }

    function onReport() {
        if (!self.hostPort) {
            return;
        }

        var count = self.countConnections('out');
        if (self.channel.emitConnectionMetrics) {
            self.channel.connectionsActiveStat.update(count, {
                'host-port': self.channel.hostPort,
                'peer-host-port': self.hostPort
            });
        }

        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }
}

inherits(TChannelPeer, EventEmitter);

TChannelPeer.prototype.invalidateScore = function invalidateScore() {
    var self = this;

    if (!self.heapElements.length) {
        return;
    }

    var score = self.handler.getScore();
    for (var i = 0; i < self.heapElements.length; i++) {
        var el = self.heapElements[i];
        el.rescore(score);
    }
};

TChannelPeer.prototype.isConnected = function isConnected(direction, identified) {
    var self = this;

    if (identified === undefined) identified = true;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (direction && conn.direction !== direction) {
            continue;
        } else if (conn.closing) {
            continue;
        } else if (conn.remoteName !== null || !identified) {
            return true;
        }
    }

    return false;
};

TChannelPeer.prototype.close = function close(callback) {
    var self = this;

    if (self.reportTimer) {
        self.timers.clearTimeout(self.reportTimer);
        self.reportTimer = null;
    }

    var counter = self.connections.length;
    if (counter) {
        self.connections.forEach(function eachConn(conn) {
            conn.close(onClose);
        });
    } else {
        callback(null);
    }
    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more peer sockets than expected', {
                    counter: counter
                });
            }
            callback(null);
        }
    }
};

TChannelPeer.prototype.getInConnection = function getInConnection() {
    var self = this;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (!conn.closing) return conn;
    }
    return null;
};

TChannelPeer.prototype.getOutConnection = function getOutConnection(preferIdentified) {
    var self = this;
    var candidate = null;
    for (var i = self.connections.length - 1; i >= 0; i--) {
        var conn = self.connections[i];
        if (conn.closing) continue;
        if (!preferIdentified) return conn; // user doesn't care, take last outgoing
        if (conn.remoteName) return conn; // user wanted an identified channel, and we found one
        if (!candidate) candidate = conn; // we'll fallback to returning this if we can't find an identified one
    }
    return candidate;
};

TChannelPeer.prototype.getIdentifiedOutConnection = function getIdentifiedOutConnection() {
    var self = this;
    return self.getOutConnection(true);
};

TChannelPeer.prototype.countConnections = function countConnections(direction) {
    var self = this;
    if (!direction) {
        return self.connections.length;
    }

    var count = 0;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (conn.direction === direction) {
            count++;
        }
    }

    return count;
};

TChannelPeer.prototype.connect = function connect(outOnly) {
    var self = this;
    var conn = self.getIdentifiedOutConnection();
    if (!conn || (outOnly && conn.direction !== 'out')) {
        var socket = self.makeOutSocket();
        conn = self.makeOutConnection(socket);
        self.addConnection(conn);
    }
    return conn;
};

TChannelPeer.prototype.waitForIdentified =
function waitForIdentified(callback) {
    var self = this;

    var conn = self.connect();

    if (conn.closing) {
        callback(conn.closeError);
    } else if (conn.remoteName) {
        callback(null);
    } else {
        self._waitForIdentified(conn, callback);
    }
};

TChannelPeer.prototype._waitForIdentified =
function _waitForIdentified(conn, callback) {
    var self = this;

    self.pendingIdentified++;
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);
    conn.identifiedEvent.on(onIdentified);
    self.invalidateScore();

    function onConnectionError(err) {
        finish(err);
    }

    function onConnectionClose(err) {
        finish(err);
    }

    function onIdentified() {
        finish(null);
    }

    function finish(err) {
        self.pendingIdentified = 0;
        conn.errorEvent.removeListener(onConnectionError);
        conn.closeEvent.removeListener(onConnectionClose);
        conn.identifiedEvent.removeListener(onIdentified);
        self.invalidateScore();
        callback(err);
    }
};

TChannelPeer.prototype.request = function peerRequest(options) {
    var self = this;
    options.timeout = options.timeout || Request.defaultTimeout;
    return self.connect().request(options);
};

TChannelPeer.prototype.addConnection = function addConnection(conn) {
    var self = this;
    // TODO: first approx alert for self.connections.length > 2
    // TODO: second approx support pruning
    if (conn.direction === 'out') {
        self.connections.push(conn);
    } else {
        self.connections.unshift(conn);
    }
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);

    self._maybeInvalidateScore();
    if (!conn.remoteName) {
        // TODO: could optimize if handler had a way of saying "would a new
        // identified connection change your Tier?"
        conn.identifiedEvent.on(onIdentified);
    }

    return conn;

    function onIdentified() {
        conn.identifiedEvent.removeListener(onIdentified);
        self._maybeInvalidateScore();
    }

    function onConnectionError(err) {
        removeConnection(err);
    }

    function onConnectionClose() {
        removeConnection(null);
    }

    function removeConnection(err) {
        conn.closeEvent.removeListener(onConnectionClose);
        conn.errorEvent.removeListener(onConnectionError);
        conn.identifiedEvent.removeListener(onIdentified);
        if (err) {
            var loggerInfo = {
                error: err,
                direction: conn.direction,
                remoteName: conn.remoteName,
                socketRemoteAddr: conn.socketRemoteAddr
            };

            var codeName = errors.classify(err);
            if (codeName === 'Timeout') {
                self.logger.warn('Got a connection error', loggerInfo);
            } else {
                self.logger.error('Got an unexpected connection error', loggerInfo);
            }
        }

        self.removeConnection(conn);
    }
};

TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
    var self = this;

    var ret = null;

    var index = self.connections ? self.connections.indexOf(conn) : -1;
    if (index !== -1) {
        ret = self.connections.splice(index, 1)[0];
    }

    self._maybeInvalidateScore();

    return ret;
};

TChannelPeer.prototype.makeOutSocket = function makeOutSocket() {
    var self = this;
    var parts = self.hostPort.split(':');
    assert(parts.length === 2, 'invalid destination');
    var host = parts[0];
    var port = parts[1];
    assert(host !== '0.0.0.0', 'cannot connect to ephemeral peer');
    assert(port !== '0', 'cannot connect to dynamic port');
    var socket = net.createConnection({host: host, port: port});
    return socket;
};

TChannelPeer.prototype.makeOutConnection = function makeOutConnection(socket) {
    var self = this;
    var chan = self.channel.topChannel || self.channel;
    var conn = new TChannelConnection(chan, socket, 'out', self.hostPort);
    self.allocConnectionEvent.emit(self, conn);
    return conn;
};

TChannelPeer.prototype.outPendingWeightedRandom = function outPendingWeightedRandom() {
    // Returns a score in the range from 0 to 1, where it is preferable to use
    // a peer with a higher score over one with a lower score.
    // This range is divided among an infinite set of subranges corresponding
    // to peers with the same number of pending requests.
    // So, the range (1/2, 1) is reserved for peers with 0 pending connections.
    // The range (1/4, 1/2) is reserved for peers with 1 pending connections.
    // The range (1/8, 1/4) is reserved for peers with 2 pending connections.
    // Ad nauseam.
    // Within each equivalence class, each peer receives a uniform random
    // value.
    //
    // The previous score was a weighted random variable:
    //   random() ** (1 + pending)
    // This had the attribute that a less loaded peer was merely more likely to
    // be chosen over a more loaded peer.
    // We observed with the introduction of a heap, that a less favored peer
    // would have its score less frequently re-evaluated.
    // An emergent behavior was that scores would, over time, be squeezed
    // toward zero and the least favored peer would remain the least favored
    // for ever increasing durations.
    //
    // This remains true with this algorithm, within each equivalence class.
    var self = this;
    var pending = self.pendingIdentified + self.countOutPending();
    var max = Math.pow(0.5, pending);
    var min = max / 2;
    var diff = max - min;
    return min + diff * self.random();
};

TChannelPeer.prototype.countOutPending = function countOutPending() {
    var self = this;
    var pending = 0;
    for (var index = 0; index < self.connections.length; index++) {
        var connPending = self.connections[index].ops.getPending();

        pending += connPending.out;
    }
    return pending;
};

// TODO: on connection #getScore impacting event
// - on identified

// Called on connection change event
TChannelPeer.prototype._maybeInvalidateScore = function _maybeInvalidateScore() {
    var self = this;

    if (self.handler.getTier() !== self.handler.lastTier) {
        self.invalidateScore();
    }
};

TChannelPeer.prototype.getScore = function getScore() {
    var self = this;
    return self.handler.getScore();
};

module.exports = TChannelPeer;
<commit_msg>TChannelPeer: support changing the score strategy<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');
var net = require('net');

var TChannelConnection = require('./connection');
var errors = require('./errors');
var Request = require('./request');
var PreferOutgoingHandler = require('./peer_score_strategies.js').PreferOutgoingHandler;

var DEFAULT_REPORT_INTERVAL = 1000;

function TChannelPeer(channel, hostPort, options) {
    if (!(this instanceof TChannelPeer)) {
        return new TChannelPeer(channel, hostPort, options);
    }
    var self = this;
    EventEmitter.call(self);

    self.stateChangedEvent = self.defineEvent('stateChanged');
    self.allocConnectionEvent = self.defineEvent('allocConnection');

    assert(hostPort !== '0.0.0.0:0', 'Cannot create ephemeral peer');

    self.channel = channel;
    self.logger = self.channel.logger;
    self.timers = self.channel.timers;
    self.random = self.channel.random;
    self.options = options || {};
    self.hostPort = hostPort;
    self.connections = [];
    self.pendingIdentified = 0;
    self.heapElements = [];
    self.handler = null;

    self.reportInterval = self.options.reportInterval || DEFAULT_REPORT_INTERVAL;
    if (self.reportInterval > 0 && self.channel.emitConnectionMetrics) {
        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }

    self.setScoreStrategy(PreferOutgoingHandler);

    function onReport() {
        if (!self.hostPort) {
            return;
        }

        var count = self.countConnections('out');
        if (self.channel.emitConnectionMetrics) {
            self.channel.connectionsActiveStat.update(count, {
                'host-port': self.channel.hostPort,
                'peer-host-port': self.hostPort
            });
        }

        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }
}

inherits(TChannelPeer, EventEmitter);

TChannelPeer.prototype.setScoreStrategy = function setScoreStrategy(ScoreStrategy) {
    var self = this;

    self.handler = new ScoreStrategy(self);
};

TChannelPeer.prototype.invalidateScore = function invalidateScore() {
    var self = this;

    if (!self.heapElements.length) {
        return;
    }

    var score = self.handler.getScore();
    for (var i = 0; i < self.heapElements.length; i++) {
        var el = self.heapElements[i];
        el.rescore(score);
    }
};

TChannelPeer.prototype.isConnected = function isConnected(direction, identified) {
    var self = this;

    if (identified === undefined) identified = true;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (direction && conn.direction !== direction) {
            continue;
        } else if (conn.closing) {
            continue;
        } else if (conn.remoteName !== null || !identified) {
            return true;
        }
    }

    return false;
};

TChannelPeer.prototype.close = function close(callback) {
    var self = this;

    if (self.reportTimer) {
        self.timers.clearTimeout(self.reportTimer);
        self.reportTimer = null;
    }

    var counter = self.connections.length;
    if (counter) {
        self.connections.forEach(function eachConn(conn) {
            conn.close(onClose);
        });
    } else {
        callback(null);
    }
    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more peer sockets than expected', {
                    counter: counter
                });
            }
            callback(null);
        }
    }
};

TChannelPeer.prototype.getInConnection = function getInConnection() {
    var self = this;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (!conn.closing) return conn;
    }
    return null;
};

TChannelPeer.prototype.getOutConnection = function getOutConnection(preferIdentified) {
    var self = this;
    var candidate = null;
    for (var i = self.connections.length - 1; i >= 0; i--) {
        var conn = self.connections[i];
        if (conn.closing) continue;
        if (!preferIdentified) return conn; // user doesn't care, take last outgoing
        if (conn.remoteName) return conn; // user wanted an identified channel, and we found one
        if (!candidate) candidate = conn; // we'll fallback to returning this if we can't find an identified one
    }
    return candidate;
};

TChannelPeer.prototype.getIdentifiedOutConnection = function getIdentifiedOutConnection() {
    var self = this;
    return self.getOutConnection(true);
};

TChannelPeer.prototype.countConnections = function countConnections(direction) {
    var self = this;
    if (!direction) {
        return self.connections.length;
    }

    var count = 0;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (conn.direction === direction) {
            count++;
        }
    }

    return count;
};

TChannelPeer.prototype.connect = function connect(outOnly) {
    var self = this;
    var conn = self.getIdentifiedOutConnection();
    if (!conn || (outOnly && conn.direction !== 'out')) {
        var socket = self.makeOutSocket();
        conn = self.makeOutConnection(socket);
        self.addConnection(conn);
    }
    return conn;
};

TChannelPeer.prototype.waitForIdentified =
function waitForIdentified(callback) {
    var self = this;

    var conn = self.connect();

    if (conn.closing) {
        callback(conn.closeError);
    } else if (conn.remoteName) {
        callback(null);
    } else {
        self._waitForIdentified(conn, callback);
    }
};

TChannelPeer.prototype._waitForIdentified =
function _waitForIdentified(conn, callback) {
    var self = this;

    self.pendingIdentified++;
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);
    conn.identifiedEvent.on(onIdentified);
    self.invalidateScore();

    function onConnectionError(err) {
        finish(err);
    }

    function onConnectionClose(err) {
        finish(err);
    }

    function onIdentified() {
        finish(null);
    }

    function finish(err) {
        self.pendingIdentified = 0;
        conn.errorEvent.removeListener(onConnectionError);
        conn.closeEvent.removeListener(onConnectionClose);
        conn.identifiedEvent.removeListener(onIdentified);
        self.invalidateScore();
        callback(err);
    }
};

TChannelPeer.prototype.request = function peerRequest(options) {
    var self = this;
    options.timeout = options.timeout || Request.defaultTimeout;
    return self.connect().request(options);
};

TChannelPeer.prototype.addConnection = function addConnection(conn) {
    var self = this;
    // TODO: first approx alert for self.connections.length > 2
    // TODO: second approx support pruning
    if (conn.direction === 'out') {
        self.connections.push(conn);
    } else {
        self.connections.unshift(conn);
    }
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);

    self._maybeInvalidateScore();
    if (!conn.remoteName) {
        // TODO: could optimize if handler had a way of saying "would a new
        // identified connection change your Tier?"
        conn.identifiedEvent.on(onIdentified);
    }

    return conn;

    function onIdentified() {
        conn.identifiedEvent.removeListener(onIdentified);
        self._maybeInvalidateScore();
    }

    function onConnectionError(err) {
        removeConnection(err);
    }

    function onConnectionClose() {
        removeConnection(null);
    }

    function removeConnection(err) {
        conn.closeEvent.removeListener(onConnectionClose);
        conn.errorEvent.removeListener(onConnectionError);
        conn.identifiedEvent.removeListener(onIdentified);
        if (err) {
            var loggerInfo = {
                error: err,
                direction: conn.direction,
                remoteName: conn.remoteName,
                socketRemoteAddr: conn.socketRemoteAddr
            };

            var codeName = errors.classify(err);
            if (codeName === 'Timeout') {
                self.logger.warn('Got a connection error', loggerInfo);
            } else {
                self.logger.error('Got an unexpected connection error', loggerInfo);
            }
        }

        self.removeConnection(conn);
    }
};

TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
    var self = this;

    var ret = null;

    var index = self.connections ? self.connections.indexOf(conn) : -1;
    if (index !== -1) {
        ret = self.connections.splice(index, 1)[0];
    }

    self._maybeInvalidateScore();

    return ret;
};

TChannelPeer.prototype.makeOutSocket = function makeOutSocket() {
    var self = this;
    var parts = self.hostPort.split(':');
    assert(parts.length === 2, 'invalid destination');
    var host = parts[0];
    var port = parts[1];
    assert(host !== '0.0.0.0', 'cannot connect to ephemeral peer');
    assert(port !== '0', 'cannot connect to dynamic port');
    var socket = net.createConnection({host: host, port: port});
    return socket;
};

TChannelPeer.prototype.makeOutConnection = function makeOutConnection(socket) {
    var self = this;
    var chan = self.channel.topChannel || self.channel;
    var conn = new TChannelConnection(chan, socket, 'out', self.hostPort);
    self.allocConnectionEvent.emit(self, conn);
    return conn;
};

TChannelPeer.prototype.outPendingWeightedRandom = function outPendingWeightedRandom() {
    // Returns a score in the range from 0 to 1, where it is preferable to use
    // a peer with a higher score over one with a lower score.
    // This range is divided among an infinite set of subranges corresponding
    // to peers with the same number of pending requests.
    // So, the range (1/2, 1) is reserved for peers with 0 pending connections.
    // The range (1/4, 1/2) is reserved for peers with 1 pending connections.
    // The range (1/8, 1/4) is reserved for peers with 2 pending connections.
    // Ad nauseam.
    // Within each equivalence class, each peer receives a uniform random
    // value.
    //
    // The previous score was a weighted random variable:
    //   random() ** (1 + pending)
    // This had the attribute that a less loaded peer was merely more likely to
    // be chosen over a more loaded peer.
    // We observed with the introduction of a heap, that a less favored peer
    // would have its score less frequently re-evaluated.
    // An emergent behavior was that scores would, over time, be squeezed
    // toward zero and the least favored peer would remain the least favored
    // for ever increasing durations.
    //
    // This remains true with this algorithm, within each equivalence class.
    var self = this;
    var pending = self.pendingIdentified + self.countOutPending();
    var max = Math.pow(0.5, pending);
    var min = max / 2;
    var diff = max - min;
    return min + diff * self.random();
};

TChannelPeer.prototype.countOutPending = function countOutPending() {
    var self = this;
    var pending = 0;
    for (var index = 0; index < self.connections.length; index++) {
        var connPending = self.connections[index].ops.getPending();

        pending += connPending.out;
    }
    return pending;
};

// TODO: on connection #getScore impacting event
// - on identified

// Called on connection change event
TChannelPeer.prototype._maybeInvalidateScore = function _maybeInvalidateScore() {
    var self = this;

    if (self.handler.getTier() !== self.handler.lastTier) {
        self.invalidateScore();
    }
};

TChannelPeer.prototype.getScore = function getScore() {
    var self = this;
    return self.handler.getScore();
};

module.exports = TChannelPeer;
<|endoftext|>


<commit_before><commit_msg>Reverse confusing test names. [ci skip]<commit_after><|endoftext|>