<commit_before>/**
  * Implementation of the graph using JavaScript
  * The graph is represented in the form of adjecency list
  *
  * author: Przemyslaw Jazlo <przemek@devrunner.pl>
  *
  */
var Graph = function(s){

	"use strict";

	// graph settings
	var settings = {};

	// default settings
	var default_settings = {
		directed: true, multiedge: false, selfloops: false, weighted: false,
	};

	// initialize settings
	(function init_settings(){
		if(typeof s === 'undefined') {
			settings = default_settings;
		} else {
			settings['directed'] = (typeof s['directed'] === 'undefined') ? default_settings['directed'] : s['directed'];
			settings['multiedge'] = (typeof s['multiedge'] === 'undefined') ? default_settings['multiedge'] : s['multiedge'];
			settings['selfloops'] = (typeof s['selfloops'] === 'undefined') ? default_settings['selfloops'] : s['selfloops'];
			settings['weighted'] = (typeof s['weighted'] === 'undefined') ? default_settings['weighted'] : s['weighted'];
		}
	})();

	// hashmap of vertices
	var vertices = {};

	// vertex constructor function
	function Vertex(key, data){
		this.key = key;
		this.data = data;
		this.neighbours = {}; // adjecency list for the vertex
	}
	Vertex.prototype = {
		getKey: function(){
			return this.key;
		},
		addNeighbour: function(vKey, weight){
			this.neighbours[vKey] = weight;
		}
	};

	/**
	  * Returns number of vertices in the graph
	  */
	function numVertices(){
		return Object.keys(vertices).length;
	}

	/**
	  * Returns number of edges in the graph
	  */
	function numEdges(){
		var vertexCount = 0;

		for(let vKey in vertices){
			vertexCount += Object.keys(vertices[vKey].neighbours).length;
		}
		if (settings['directed'] === false){
			vertexCount /= 2;
		}
		return vertexCount;
	}

	/**
	  * Checks if the given vertex exists
	  */
	function vertexExists(vKey){
		if(typeof vertices[vKey] !== 'undefined') return true;
		return false;
	}

	/**
	  * Checks if the edge from v1 to v2 exists
	  */
	function edgeExists(v1Key, v2Key){
		if(!vertexExists(v1Key) || !vertexExists(v2Key)) return false;

		if(typeof vertices[v1Key].neighbours[v2Key] !== 'undefined'){
			return true;
		}
		return false;
	}

	/**
	  * Returns degree (number of edges connected) of the given vertex
	  */
	function degree(vKey){
		if( !vertexExists(vKey) ) throw "Vertex " + vKey + " does not exist.";

		return Object.keys(vertices[vKey].neighbours).length;
	}

	/**
	  * Clears the graph (removes all vertices and edges)
	  */
	function clear(){
		vertices = {};
	}

	/**
	  * Returns the string representation of the graph's adjecency list
	  */
	function toString(){
		var s = '';
		for(let vKey in vertices){
			let v = vertices[vKey];
			var numEdges = Object.keys(v.neighbours).length, i=0;
			s += vKey;
			s += ' --> [';
			for (let eKey in v.neighbours){
				s += eKey;
				++i;
				if(i < numEdges) s += ', ';
			}
			s += ']\n';
		}
		return s;
	}

	/**
	  * Inserts new vertex to the graph, key must be unique
	  */
	function addVertex(key, data){

		// check if key is provided
		if(typeof key === 'undefined'){
			throw "Vertex must have a unique key.";
		}

		// check if vertex already exists
		if( vertexExists(key) ) {
			throw "Vertex " + key + "' already exists.";
		}

		// push to the vertices hash map
		vertices[key] = new Vertex(key, data);
		return true;
	}

	/**
	  * Removes vertex from the graph
	  */
	function deleteVertex(vKey){

		if(typeof vKey === 'undefined'){
			throw "Must privide vertex key.";
		}
		if( !vertexExists(vKey) ) throw "Vertex " + vKey + " does not exist.";

		// delete vertex from vertices object
		delete vertices[vKey];

		// delete all edges where vKey was an end vertex
		for(let v in vertices){
			delete vertices[v].neighbours[vKey];
		}
		return true;
	}

	/**
	  * Inserts new edge connecting two vertices given by the key
	  */
	function addEdge(v1Key, v2Key, weight){

		if( !vertexExists(v1Key) ) throw "Vertex " + v1Key + " does not exist.";

		if( !vertexExists(v2Key) ) throw "Vertex " + v2Key + " does not exist.";

		// handle selfloops
		if(v1Key === v2Key && settings['selfloops'] === false){
			throw "Self-loops are not allowed.";
		}

		// handle multiedges
		if(edgeExists(v1Key, v2Key) && settings['multiedge'] === false){
			throw "Multiedges are not allowed.";
		}

		// handle missing weights
		if(typeof weight === 'undefined' && settings['weighted'] === true){
			throw "Edges must have weights.";
		}

		// set weights
		weight = (settings['weighted'] === true) ? weight : true;

		// set v2 as v1's neighbour
		vertices[v1Key].addNeighbour(v2Key, weight);

		// for undirected graph add edge from v2 to v1 as well
		if(settings['directed'] === false){
			vertices[v2Key].addNeighbour(v1Key, weight);
		}
		return true;
	}

	/**
	  * Deletes an edge
	  */
	function deleteEdge(v1Key, v2Key){

		if( !vertexExists(v1Key) ) throw "Vertex " + v1Key + " does not exist.";
		if( !vertexExists(v2Key) ) throw "Vertex " + v2Key + " does not exist.";

		if(typeof vertices[v1Key].neighbours[v2Key] !== 'undefined'){

			// remove connection from v1 to v2
			delete vertices[v1Key].neighbours[v2Key];

			// for undirected graphs, remove connection from v2 to v1 as well
			if(settings['directed'] === false){
				delete vertices[v2Key].neighbours[v1Key];
			}
			return true;
		}
		return false;
	}

	/**
	  * Finds all adjacent vertices for the given vertex
	  */
	function getAdjacentVertices(vKey){

		if( !vertexExists(vKey) ) throw "Vertex " + vKey + " does not exist.";

		return vertices[vKey].neighbours;
	}

	/**
	  * Returns an array of graphs vertices' keys
	  */
	function getVertices(){
		return Object.keys(vertices);
	}

	/**
	  * Graph public API
	  */
	var publicApi = {
		numVertices: numVertices,
		numEdges: numEdges,
		vertexExists: vertexExists,
		edgeExists: edgeExists,
		degree: degree,
		clear: clear,
		toString: toString,
		addVertex: addVertex,
		deleteVertex: deleteVertex,
		addEdge: addEdge,
		deleteEdge: deleteEdge,
		getAdjacentVertices: getAdjacentVertices,
		getVertices: getVertices,
	};

	return publicApi;
};

/**
  * DFS path finder
  */
var DepthFirstPaths = function(graph, startVertex){

	"use strict";

	if(typeof graph === 'undefined' || typeof graph != 'object') throw "Provide a graph object.";
	if(typeof startVertex === 'undefined' || !graph.vertexExists(startVertex)) throw "Provide an existing vertex key.";

	var visited, edgeTo;

	run(startVertex);

	/** Run the algorithm
	  */
	function run(v){
		visited = {};
		edgeTo = {};
		dfs(v);
	}

	/**
	  * Recursive DFS
	  */
	function dfs(v){

		visited[v] = true;
		var adjList = graph.getAdjacentVertices(v);

		for(let w in adjList){
			if(!visited[w]){
				dfs(w);
				edgeTo[w] = v;
			}
		}
	}

	/**
	  * Returns true if the given vertex is connected to the start vertex
	  */
	function isConnected(v){
		if( !graph.vertexExists(v) ) throw "Vertex " + v + " does not exist.";
		return (typeof visited[v] !== 'undefined' && visited[v] == true);
	}

	/**
	  * Returns a DFS path from start vertex to vertex v
	  */
	function pathTo(v){

		if( !isConnected(v) ) return null;

		var stack = [];
		for(let x=v; x != startVertex; x = edgeTo[x]){
			stack.push(x);
		}
		stack.push(startVertex);
		return stack;
	}

	var publicApi = {
		run: run,
		isConnected: isConnected,
		pathTo: pathTo,
	};

	return publicApi;
};

/**
  * BFS path finder
  */
var BreadthFirstPaths = function(graph, startVertex){

	"use strict";

	if(typeof graph === 'undefined' || typeof graph != 'object') throw "Provide a graph object.";
	if(typeof startVertex === 'undefined' || !graph.vertexExists(startVertex)) throw "Provide an existing vertex key.";

	var visited, edgeTo;

	// initially find paths from start vertex
	run(startVertex);

	/**
	  * Run the algorithm
	  */
	function run(v){
		visited = {};
		edgeTo = {};
		bfs(v);
	}

	/**
	  * BFS traversal using queue
	  */
	function bfs(s){

		var queue = [];
		queue.push(s);
		visited[s] = true;

		while(queue.length > 0){

			let v = queue.shift();
			let adj = graph.getAdjacentVertices(v);

			for(let w in adj){
				if(!visited[w]){
					queue.push(w);
					visited[w] = true;
					edgeTo[w] = v;
				}
			}
		}
	}

	/**
	  * Returns true if the given vertex is connected to the start vertex
	  */
	function isConnected(v){
		if( !graph.vertexExists(v) ) throw "Vertex " + v + " does not exist.";
		return (typeof visited[v] !== 'undefined' && visited[v] == true);
	}

	/**
	  * Returns a DFS path from start vertex to vertex v
	  */
	function pathTo(v){

		if( !isConnected(v) ) return null;

		var stack = [];
		for(let x=v; x != startVertex; x = edgeTo[x]){
			stack.push(x);
		}
		stack.push(startVertex);
		return stack;
	}

	var publicApi = {
		run: run,
		isConnected: isConnected,
		pathTo: pathTo,
	};

	return publicApi;
};

// export module
module.exports = {
	Graph: Graph,
	DepthFirstPaths: DepthFirstPaths,
	BreadthFirstPaths: BreadthFirstPaths,
};
<commit_msg>DFS and BFS corrected<commit_after>/**
  * Implementation of the graph using JavaScript
  * The graph is represented in the form of adjecency list
  *
  * author: Przemyslaw Jazlo <przemek@devrunner.pl>
  *
  */
var Graph = function(s){

	"use strict";

	// graph settings
	var settings = {};

	// default settings
	var default_settings = {
		directed: true, multiedge: false, selfloops: false, weighted: false,
	};

	// initialize settings
	(function init_settings(){
		if(typeof s === 'undefined') {
			settings = default_settings;
		} else {
			settings['directed'] = (typeof s['directed'] === 'undefined') ? default_settings['directed'] : s['directed'];
			settings['multiedge'] = (typeof s['multiedge'] === 'undefined') ? default_settings['multiedge'] : s['multiedge'];
			settings['selfloops'] = (typeof s['selfloops'] === 'undefined') ? default_settings['selfloops'] : s['selfloops'];
			settings['weighted'] = (typeof s['weighted'] === 'undefined') ? default_settings['weighted'] : s['weighted'];
		}
	})();

	// hashmap of vertices
	var vertices = {};

	// vertex constructor function
	function Vertex(key, data){
		this.key = key;
		this.data = data;
		this.neighbours = {}; // adjecency list for the vertex
	}
	Vertex.prototype = {
		getKey: function(){
			return this.key;
		},
		addNeighbour: function(vKey, weight){
			this.neighbours[vKey] = weight;
		}
	};

	/**
	  * Returns number of vertices in the graph
	  */
	function numVertices(){
		return Object.keys(vertices).length;
	}

	/**
	  * Returns number of edges in the graph
	  */
	function numEdges(){
		var vertexCount = 0;

		for(let vKey in vertices){
			vertexCount += Object.keys(vertices[vKey].neighbours).length;
		}
		if (settings['directed'] === false){
			vertexCount /= 2;
		}
		return vertexCount;
	}

	/**
	  * Checks if the given vertex exists
	  */
	function vertexExists(vKey){
		if(typeof vertices[vKey] !== 'undefined') return true;
		return false;
	}

	/**
	  * Checks if the edge from v1 to v2 exists
	  */
	function edgeExists(v1Key, v2Key){
		if(!vertexExists(v1Key) || !vertexExists(v2Key)) return false;

		if(typeof vertices[v1Key].neighbours[v2Key] !== 'undefined'){
			return true;
		}
		return false;
	}

	/**
	  * Returns degree (number of edges connected) of the given vertex
	  */
	function degree(vKey){
		if( !vertexExists(vKey) ) throw "Vertex " + vKey + " does not exist.";

		return Object.keys(vertices[vKey].neighbours).length;
	}

	/**
	  * Clears the graph (removes all vertices and edges)
	  */
	function clear(){
		vertices = {};
	}

	/**
	  * Returns the string representation of the graph's adjecency list
	  */
	function toString(){
		var s = '';
		for(let vKey in vertices){
			let v = vertices[vKey];
			var numEdges = Object.keys(v.neighbours).length, i=0;
			s += vKey;
			s += ' --> [';
			for (let eKey in v.neighbours){
				s += eKey;
				++i;
				if(i < numEdges) s += ', ';
			}
			s += ']\n';
		}
		return s;
	}

	/**
	  * Inserts new vertex to the graph, key must be unique
	  */
	function addVertex(key, data){

		// check if key is provided
		if(typeof key === 'undefined'){
			throw "Vertex must have a unique key.";
		}

		// check if vertex already exists
		if( vertexExists(key) ) {
			throw "Vertex " + key + "' already exists.";
		}

		// push to the vertices hash map
		vertices[key] = new Vertex(key, data);
		return true;
	}

	/**
	  * Removes vertex from the graph
	  */
	function deleteVertex(vKey){

		if(typeof vKey === 'undefined'){
			throw "Must privide vertex key.";
		}
		if( !vertexExists(vKey) ) throw "Vertex " + vKey + " does not exist.";

		// delete vertex from vertices object
		delete vertices[vKey];

		// delete all edges where vKey was an end vertex
		for(let v in vertices){
			delete vertices[v].neighbours[vKey];
		}
		return true;
	}

	/**
	  * Inserts new edge connecting two vertices given by the key
	  */
	function addEdge(v1Key, v2Key, weight){

		if( !vertexExists(v1Key) ) throw "Vertex " + v1Key + " does not exist.";

		if( !vertexExists(v2Key) ) throw "Vertex " + v2Key + " does not exist.";

		// handle selfloops
		if(v1Key === v2Key && settings['selfloops'] === false){
			throw "Self-loops are not allowed.";
		}

		// handle multiedges
		if(edgeExists(v1Key, v2Key) && settings['multiedge'] === false){
			throw "Multiedges are not allowed.";
		}

		// handle missing weights
		if(typeof weight === 'undefined' && settings['weighted'] === true){
			throw "Edges must have weights.";
		}

		// set weights
		weight = (settings['weighted'] === true) ? weight : true;

		// set v2 as v1's neighbour
		vertices[v1Key].addNeighbour(v2Key, weight);

		// for undirected graph add edge from v2 to v1 as well
		if(settings['directed'] === false){
			vertices[v2Key].addNeighbour(v1Key, weight);
		}
		return true;
	}

	/**
	  * Deletes an edge
	  */
	function deleteEdge(v1Key, v2Key){

		if( !vertexExists(v1Key) ) throw "Vertex " + v1Key + " does not exist.";
		if( !vertexExists(v2Key) ) throw "Vertex " + v2Key + " does not exist.";

		if(typeof vertices[v1Key].neighbours[v2Key] !== 'undefined'){

			// remove connection from v1 to v2
			delete vertices[v1Key].neighbours[v2Key];

			// for undirected graphs, remove connection from v2 to v1 as well
			if(settings['directed'] === false){
				delete vertices[v2Key].neighbours[v1Key];
			}
			return true;
		}
		return false;
	}

	/**
	  * Finds all adjacent vertices for the given vertex
	  */
	function getAdjacentVertices(vKey){

		if( !vertexExists(vKey) ) throw "Vertex " + vKey + " does not exist.";

		return vertices[vKey].neighbours;
	}

	/**
	  * Returns an array of keys of graph vertices
	  */
	function getVertices(){
		return Object.keys(vertices);
	}

	/**
	  * Graph public API
	  */
	var publicApi = {
		numVertices: numVertices,
		numEdges: numEdges,
		vertexExists: vertexExists,
		edgeExists: edgeExists,
		degree: degree,
		clear: clear,
		toString: toString,
		addVertex: addVertex,
		deleteVertex: deleteVertex,
		addEdge: addEdge,
		deleteEdge: deleteEdge,
		getAdjacentVertices: getAdjacentVertices,
		getVertices: getVertices,
	};

	return publicApi;
};

/**
  * DFS path finder
  */
var DepthFirstPaths = function(graph, startVertex){

	"use strict";

	if(typeof graph === 'undefined' || typeof graph != 'object')
		throw "Provide a graph object.";

	var visited, edgeTo;

	run(startVertex);

	/** Run the algorithm
	  */
	function run(v){
		if(typeof v === 'undefined' || !graph.vertexExists(v))
			throw "Provide an existing vertex key.";
		visited = {};
		edgeTo = {};
		dfs(v);
	}

	/**
	  * Recursive DFS
	  */
	function dfs(v){

		visited[v] = true;
		var adjList = graph.getAdjacentVertices(v);

		for(let w in adjList){
			if(!visited[w]){
				dfs(w);
				edgeTo[w] = v;
			}
		}
	}

	/**
	  * Returns true if the given vertex is connected to the start vertex
	  */
	function isConnected(v){
		if( !graph.vertexExists(v) ) throw "Vertex " + v + " does not exist.";
		return (typeof visited[v] !== 'undefined' && visited[v] == true);
	}

	/**
	  * Returns a DFS path from start vertex to vertex v
	  */
	function pathTo(v){

		if( !isConnected(v) ) return null;

		var stack = [];
		for(let x=v; x != startVertex; x = edgeTo[x]){
			stack.push(x);
		}
		stack.push(startVertex);
		return stack;
	}

	var publicApi = {
		run: run,
		isConnected: isConnected,
		pathTo: pathTo,
	};

	return publicApi;
};

/**
  * BFS path finder
  */
var BreadthFirstPaths = function(graph, startVertex){

	"use strict";

	if(typeof graph === 'undefined' || typeof graph != 'object')
		throw "Provide a graph object.";

	var visited, edgeTo;

	// initially find paths from start vertex
	run(startVertex);

	/**
	  * Run the algorithm
	  */
	function run(v){
		if(typeof v === 'undefined' || !graph.vertexExists(v))
			throw "Provide an existing vertex key.";
		visited = {};
		edgeTo = {};
		bfs(v);
	}

	/**
	  * BFS traversal using queue
	  */
	function bfs(s) {

		var queue = [];

		// mark source vertex as visited and add it to the queue
		queue.push(s);
		visited[s] = true;

		while(queue.length > 0){

			let v = queue.shift();
			let adj = graph.getAdjacentVertices(v);

			for(let w in adj){
				if(!visited[w]){
					queue.push(w);
					visited[w] = true;
					edgeTo[w] = v;
				}
			}
		}
	}

	/**
	  * Returns true if the given vertex is connected to the start vertex
	  */
	function isConnected(v){
		if( !graph.vertexExists(v) ) throw "Vertex " + v + " does not exist.";
		return (typeof visited[v] !== 'undefined' && visited[v] == true);
	}

	/**
	  * Returns a DFS path from start vertex to vertex v
	  */
	function pathTo(v){

		if( !isConnected(v) ) return null;

		var stack = [];
		for(let x=v; x != startVertex; x = edgeTo[x]){
			stack.push(x);
		}
		stack.push(startVertex);
		return stack;
	}

	var publicApi = {
		run: run,
		isConnected: isConnected,
		pathTo: pathTo,
	};

	return publicApi;
};

// export module
module.exports = {
	Graph: Graph,
	DepthFirstPaths: DepthFirstPaths,
	BreadthFirstPaths: BreadthFirstPaths,
};<|endoftext|>


<commit_before>var commandMap = { // Key that can be typed
  ESCAPE:'KEY_LEFT_ESC',
  ESC:'KEY_LEFT_ESC',
  MENU:'229',
  END:'KEY_END',
  SPACE:'\' \'',
  TAB:'KEY_TAB',
  PRINTSCREEN:'206',
  ENTER:'KEY_RETURN',
  UPARROW:'KEY_UP_ARROW',
  DOWNARROW:'KEY_DOWN_ARROW',
  LEFTARROW:'KEY_LEFT_ARROW',
  RIGHTARROW:'KEY_RIGHT_ARROW',
  UP:'KEY_UP_ARROW',
  DOWN:'KEY_DOWN_ARROW',
  LEFT:'KEY_LEFT_ARROW',
  RIGHT:'KEY_RIGHT_ARROW',
  CAPSLOCK:'KEY_CAPS_LOCK',
  DELETE:'KEY_DELETE',
  DEL:'KEY_DELETE'
};

var comboMap = { // Key that can only be used in combos
  ALT:'KEY_LEFT_ALT',
  SHIFT:'KEY_LEFT_SHIFT',
  CTRL:'KEY_LEFT_CTRL',
  CONTROL:'KEY_LEFT_CTRL',
  GUI:'KEY_LEFT_GUI',
  WINDOWS:'KEY_LEFT_GUI',
  COMMAND:'KEY_LEFT_GUI'
};

var keyMap = { // Normal keys
  A:'97',
  B:'98',
  C:'99',
  D:'100',
  E:'101',
  F:'102',
  G:'103',
  H:'104',
  I:'105',
  J:'106',
  K:'107',
  L:'108',
  M:'109',
  N:'110',
  O:'111',
  P:'112',
  Q:'113',
  R:'114',
  S:'115',
  T:'116',
  U:'117',
  V:'118',
  W:'119',
  X:'120',
  Y:'121',
  Z:'122'
};

class Dckuinojs {
  constructor() {
    this.keyMap = keyMap;
    this.commandMap = commandMap;
    this.comboMap = comboMap;
  }

  toArduino(inputCode)
  {
    // Check if the parameter is empty or undefined
    if (inputCode === '' || inputCode === undefined)
    {
      console.error('Error: No ducky script was entered !');
      return false;
    }  // Parsing

    var parsedDucky = this._parse(inputCode);
    if (parsedDucky === '' || parsedDucky === undefined)
    {
      return false;
    }  // Returning the total uploadable script

    return '/*\n'
    + ' * Generated with <3 by Dckuino.js, an open source project !\n'
    + ' */\n'
    + '\n// Init function\n'
    + 'void setup()\n'
    + '{\n'
    + '  // Begining the stream\n'
    + '  Keyboard.begin();\n\n'
    + '  // Waiting 500ms for init\n'
    + '  delay(500);\n'
    + '\n' + parsedDucky
    + '}\n'
    + '\n'
    + 'void typeKey(int key)\n'
    + '{\n'
    + '  Keyboard.press(key);\n'
    + '  delay(50);\n'
    + '  Keyboard.release(key);\n'
    + '}\n\n'
    + '// Unused\n'
    + 'void loop() {}';
  }

  // The parsing function
   _parse(toParse)
  {
    // Init chronometer
    var timerStart = Date.now();

    var parsedScript = '';

    // Trim whitespaces
    toParse = toParse.replace(/^ +| +$/gm, "");

    // Cuting the input in lines
    var lineArray = toParse.split('\n');

    // Loop every line
    for (var i = 0; i < lineArray.length; i++)
    {
      // Line empty, skip
      if (lineArray[i] === '' || lineArray[i] === '\n')
      {
        console.log('Info: Skipped line ' + (i + 1) + ', because was empty.');
        continue;
      }

      // Var who indicates to release all at the line end
      var releaseAll = false;

      // Outputs, for REPLAY/REPEAT COMMANDS
      if (parsedOut !== undefined && parsedOut !== '')
      {
        var lastLines = parsedOut;
        var lastCount = ((lastLines.split('\n')).length + 1);
      }
      var parsedOut = '';

      // Command known
      var commandKnown = false;

      // Cutting every line in words
      var wordArray = lineArray[i].split(' ');
      var wordOne = wordArray[0];

      // Handle commands
      switch(wordOne){
        case "STRING":
          wordArray.shift();

          var textString = wordArray.join(' ');

          // Replace all '"' by '\"' and all '\' by '\\'
          textString = textString.split('\\').join('\\\\').split('"').join('\\"');
          if (textString !== '')
          {
            parsedOut += '  Keyboard.print("' + textString + '");\n';
            commandKnown = true;
          } else {
            console.error('Error: at line: ' + (i + 1) + ', STRING needs a text');
            return;
          }
          break;
        case "DELAY":
          wordArray.shift();

          if(wordArray[0] === undefined || wordArray[0] === '') {
            console.error('Error: at line: ' + (i + 1) + ', DELAY needs a time');
            return;
          }

          if (! isNaN(wordArray[0]))
          {
            parsedOut += '  delay(' + wordArray[0] + ');\n';
            commandKnown = true;
          } else {
            console.error('Error: at line: ' + (i + 1) + ', DELAY only acceptes numbers');
            return;
          }
          break;
        case "TYPE":
          wordArray.shift();

          if(wordArray[0] === undefined || wordArray[0] === '') {
            console.error('Error: at line: ' + (i + 1) + ', TYPE needs a key');
            return;
          }

          if (keyMap[wordArray[0]] !== undefined)
          {
            commandKnown = true;
            // Replace the DuckyScript key by the Arduino key name
            parsedOut += '  typeKey(' + keyMap[wordArray[0]] + ');\n';
          } else {
            console.error('Error: Unknown letter \'' + wordArray[0] +'\' at line: ' + (i + 1));
            return;
          }
          break;
        case "REM":
          wordArray.shift();

          // Placing the comment to arduino code
          if (wordArray[0] !== undefined && wordArray[0] !== '')
          {
            commandKnown = true;
            parsedOut += '  // ' + wordArray.join(' ');
          } else {
            console.error('Error: at line: ' + (i + 1) + ', REM needs a comment');
            return;
          }
          break;
        case "REPEAT":
        case "REPLAY":
          wordArray.shift();

          if (wordArray[0] === undefined || wordArray[0] === '') {
            console.error('Error: at line: ' + (i + 1) + ', REPEAT/REPLAY needs a loop count');
            return;
          }

          if (lastLines === undefined)
          {
            console.error('Error: at line: ' + (i + 1) + ', nothing to repeat, this is the first line.');
            return;
          }

          if (! isNaN(wordArray[0]))
          {
            // Remove the lines we just created
            var linesTmp = parsedScript.split('\n');
            linesTmp.splice(-lastCount, lastCount);

            if (linesTmp.join('\n') === '')
              parsedScript = linesTmp.join('\n');
            else {
              parsedScript = linesTmp.join('\n') + '\n';
            }

            // Add two spaces at Begining
            lastLines = lastLines.replace(/^  /gm,'    ');

            // Replace them
            parsedOut += '  for(int i = 0; i < ' + wordArray[0] + '; i++) {\n';
            parsedOut += lastLines;
            parsedOut += '  }\n';

            commandKnown = true;
          } else {
            console.error('Error: at line: ' + (i + 1) + ', REPEAT/REPLAY only acceptes numbers');
            return;
          }
          break;
        default:
          if (wordArray.length == 1)
          {
            if (comboMap[wordArray[0]] !== undefined)
            {
              commandKnown = true;

              parsedOut += '  typeKey(' + comboMap[wordArray[0]] + ');\n';
            }else if (commandMap[wordArray[0]] !== undefined) {
              commandKnown = true;

              parsedOut += '  typeKey(' + commandMap[wordArray[0]] + ');\n';
            }else {
              commandKnown = false;
              break;
            }
            wordArray.shift();
          }
          while (wordArray.length){
            if (comboMap[wordArray[0]] !== undefined)
            {
              commandKnown = true;
              releaseAll = true;

              parsedOut += '  Keyboard.press(' + comboMap[wordArray[0]] + ');\n';
            }else if (commandMap[wordArray[0]] !== undefined) {
              commandKnown = true;
              releaseAll = true;

              parsedOut += '  Keyboard.press(' + commandMap[wordArray[0]] + ');\n';
            }else if (keyMap[wordArray[0]] !== undefined) {
              commandKnown = true;
              releaseAll = true;

              parsedOut += '  Keyboard.press(' + keyMap[wordArray[0]] + ');\n';
            }else {
              commandKnown = false;
              break;
            }
            wordArray.shift();
          }
      }

      if (!commandKnown)
      {
        console.error('Error: Unknown command or key \'' + wordArray[0] + '\' at line: ' + (i + 1) + '.');
        return;
      }

      // If we need to release keys, we do
      if (releaseAll)
        parsedOut += '  Keyboard.releaseAll();\n';

      parsedScript += parsedOut; // Add what we parsed
      if (i != (lineArray.length - 1))
        parsedScript += '\n'; // Add new line if not the last line
    }

    var timerEnd = Date.now();
    var timePassed = new Date(timerEnd - timerStart);

    console.log('Done parsed ' + (lineArray.length) + ' lines in ' + timePassed.getMilliseconds() + 'ms');
    return parsedScript;
  }
}
<commit_msg>Reswiched to lowercase keymap and added APP to mirror MENU.<commit_after>var commandMap = { // Key that can be typed
  ESCAPE:'KEY_LEFT_ESC',
  ESC:'KEY_LEFT_ESC',
  MENU:'229',
  APP:'229',
  END:'KEY_END',
  SPACE:'\' \'',
  TAB:'KEY_TAB',
  PRINTSCREEN:'206',
  ENTER:'KEY_RETURN',
  UPARROW:'KEY_UP_ARROW',
  DOWNARROW:'KEY_DOWN_ARROW',
  LEFTARROW:'KEY_LEFT_ARROW',
  RIGHTARROW:'KEY_RIGHT_ARROW',
  UP:'KEY_UP_ARROW',
  DOWN:'KEY_DOWN_ARROW',
  LEFT:'KEY_LEFT_ARROW',
  RIGHT:'KEY_RIGHT_ARROW',
  CAPSLOCK:'KEY_CAPS_LOCK',
  DELETE:'KEY_DELETE',
  DEL:'KEY_DELETE'
};

var comboMap = { // Key that can only be used in combos
  ALT:'KEY_LEFT_ALT',
  SHIFT:'KEY_LEFT_SHIFT',
  CTRL:'KEY_LEFT_CTRL',
  CONTROL:'KEY_LEFT_CTRL',
  GUI:'KEY_LEFT_GUI',
  WINDOWS:'KEY_LEFT_GUI',
  COMMAND:'KEY_LEFT_GUI'
};

var keyMap = { // Normal keys
  a:'97',
  b:'98',
  c:'99',
  d:'100',
  e:'101',
  f:'102',
  g:'103',
  h:'104',
  i:'105',
  j:'106',
  k:'107',
  l:'108',
  m:'109',
  n:'110',
  o:'111',
  p:'112',
  q:'113',
  r:'114',
  s:'115',
  t:'116',
  u:'117',
  v:'118',
  w:'119',
  x:'120',
  y:'121',
  z:'122'
};

class Dckuinojs {
  constructor() {
    this.keyMap = keyMap;
    this.commandMap = commandMap;
    this.comboMap = comboMap;
  }

  toArduino(inputCode)
  {
    // Check if the parameter is empty or undefined
    if (inputCode === '' || inputCode === undefined)
    {
      console.error('Error: No ducky script was entered !');
      return false;
    }  // Parsing

    var parsedDucky = this._parse(inputCode);
    if (parsedDucky === '' || parsedDucky === undefined)
    {
      return false;
    }  // Returning the total uploadable script

    return '/*\n'
    + ' * Generated with <3 by Dckuino.js, an open source project !\n'
    + ' */\n'
    + '\n// Init function\n'
    + 'void setup()\n'
    + '{\n'
    + '  // Begining the stream\n'
    + '  Keyboard.begin();\n\n'
    + '  // Waiting 500ms for init\n'
    + '  delay(500);\n'
    + '\n' + parsedDucky
    + '}\n'
    + '\n'
    + 'void typeKey(int key)\n'
    + '{\n'
    + '  Keyboard.press(key);\n'
    + '  delay(50);\n'
    + '  Keyboard.release(key);\n'
    + '}\n\n'
    + '// Unused\n'
    + 'void loop() {}';
  }

  // The parsing function
   _parse(toParse)
  {
    // Init chronometer
    var timerStart = Date.now();

    var parsedScript = '';

    // Trim whitespaces
    toParse = toParse.replace(/^ +| +$/gm, "");

    // Cuting the input in lines
    var lineArray = toParse.split('\n');

    // Loop every line
    for (var i = 0; i < lineArray.length; i++)
    {
      // Line empty, skip
      if (lineArray[i] === '' || lineArray[i] === '\n')
      {
        console.log('Info: Skipped line ' + (i + 1) + ', because was empty.');
        continue;
      }

      // Var who indicates to release all at the line end
      var releaseAll = false;

      // Outputs, for REPLAY/REPEAT COMMANDS
      if (parsedOut !== undefined && parsedOut !== '')
      {
        var lastLines = parsedOut;
        var lastCount = ((lastLines.split('\n')).length + 1);
      }
      var parsedOut = '';

      // Command known
      var commandKnown = false;

      // Cutting every line in words
      var wordArray = lineArray[i].split(' ');
      var wordOne = wordArray[0];

      // Handle commands
      switch(wordOne){
        case "STRING":
          wordArray.shift();

          var textString = wordArray.join(' ');

          // Replace all '"' by '\"' and all '\' by '\\'
          textString = textString.split('\\').join('\\\\').split('"').join('\\"');
          if (textString !== '')
          {
            parsedOut += '  Keyboard.print("' + textString + '");\n';
            commandKnown = true;
          } else {
            console.error('Error: at line: ' + (i + 1) + ', STRING needs a text');
            return;
          }
          break;
        case "DELAY":
          wordArray.shift();

          if(wordArray[0] === undefined || wordArray[0] === '') {
            console.error('Error: at line: ' + (i + 1) + ', DELAY needs a time');
            return;
          }

          if (! isNaN(wordArray[0]))
          {
            parsedOut += '  delay(' + wordArray[0] + ');\n';
            commandKnown = true;
          } else {
            console.error('Error: at line: ' + (i + 1) + ', DELAY only acceptes numbers');
            return;
          }
          break;
        case "TYPE":
          wordArray.shift();

          if(wordArray[0] === undefined || wordArray[0] === '') {
            console.error('Error: at line: ' + (i + 1) + ', TYPE needs a key');
            return;
          }

          if (keyMap[wordArray[0]] !== undefined)
          {
            commandKnown = true;
            // Replace the DuckyScript key by the Arduino key name
            parsedOut += '  typeKey(' + keyMap[wordArray[0]] + ');\n';
          } else {
            console.error('Error: Unknown letter \'' + wordArray[0] +'\' at line: ' + (i + 1));
            return;
          }
          break;
        case "REM":
          wordArray.shift();

          // Placing the comment to arduino code
          if (wordArray[0] !== undefined && wordArray[0] !== '')
          {
            commandKnown = true;
            parsedOut += '  // ' + wordArray.join(' ');
          } else {
            console.error('Error: at line: ' + (i + 1) + ', REM needs a comment');
            return;
          }
          break;
        case "REPEAT":
        case "REPLAY":
          wordArray.shift();

          if (wordArray[0] === undefined || wordArray[0] === '') {
            console.error('Error: at line: ' + (i + 1) + ', REPEAT/REPLAY needs a loop count');
            return;
          }

          if (lastLines === undefined)
          {
            console.error('Error: at line: ' + (i + 1) + ', nothing to repeat, this is the first line.');
            return;
          }

          if (! isNaN(wordArray[0]))
          {
            // Remove the lines we just created
            var linesTmp = parsedScript.split('\n');
            linesTmp.splice(-lastCount, lastCount);

            if (linesTmp.join('\n') === '')
              parsedScript = linesTmp.join('\n');
            else {
              parsedScript = linesTmp.join('\n') + '\n';
            }

            // Add two spaces at Begining
            lastLines = lastLines.replace(/^  /gm,'    ');

            // Replace them
            parsedOut += '  for(int i = 0; i < ' + wordArray[0] + '; i++) {\n';
            parsedOut += lastLines;
            parsedOut += '  }\n';

            commandKnown = true;
          } else {
            console.error('Error: at line: ' + (i + 1) + ', REPEAT/REPLAY only acceptes numbers');
            return;
          }
          break;
        default:
          if (wordArray.length == 1)
          {
            if (comboMap[wordArray[0]] !== undefined)
            {
              commandKnown = true;

              parsedOut += '  typeKey(' + comboMap[wordArray[0]] + ');\n';
            }else if (commandMap[wordArray[0]] !== undefined) {
              commandKnown = true;

              parsedOut += '  typeKey(' + commandMap[wordArray[0]] + ');\n';
            }else {
              commandKnown = false;
              break;
            }
            wordArray.shift();
          }
          while (wordArray.length){
            if (comboMap[wordArray[0]] !== undefined)
            {
              commandKnown = true;
              releaseAll = true;

              parsedOut += '  Keyboard.press(' + comboMap[wordArray[0]] + ');\n';
            }else if (commandMap[wordArray[0]] !== undefined) {
              commandKnown = true;
              releaseAll = true;

              parsedOut += '  Keyboard.press(' + commandMap[wordArray[0]] + ');\n';
            }else if (keyMap[wordArray[0]] !== undefined) {
              commandKnown = true;
              releaseAll = true;

              parsedOut += '  Keyboard.press(' + keyMap[wordArray[0]] + ');\n';
            }else {
              commandKnown = false;
              break;
            }
            wordArray.shift();
          }
      }

      if (!commandKnown)
      {
        console.error('Error: Unknown command or key \'' + wordArray[0] + '\' at line: ' + (i + 1) + '.');
        return;
      }

      // If we need to release keys, we do
      if (releaseAll)
        parsedOut += '  Keyboard.releaseAll();\n';

      parsedScript += parsedOut; // Add what we parsed
      if (i != (lineArray.length - 1))
        parsedScript += '\n'; // Add new line if not the last line
    }

    var timerEnd = Date.now();
    var timePassed = new Date(timerEnd - timerStart);

    console.log('Done parsed ' + (lineArray.length) + ' lines in ' + timePassed.getMilliseconds() + 'ms');
    return parsedScript;
  }
}
<|endoftext|>


<commit_before>goog.provide('ol.FeatureOverlay');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.Collection');
goog.require('ol.CollectionEventType');
goog.require('ol.Feature');
goog.require('ol.feature');
goog.require('ol.render.EventType');

/**
 * @constructor
 * @param {olx.FeatureOverlayOptions=} opt_options Options.
 * @todo stability experimental
 */
ol.FeatureOverlay = function(opt_options) {

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * @private
   * @type {ol.Collection}
   */
  this.features_ = null;

  /**
   * @private
   * @type {Array.<goog.events.Key>}
   */
  this.featuresListenerKeys_ = null;

  /**
   * @private
   * @type {Object.<string, goog.events.Key>}
   */
  this.featureChangeListenerKeys_ = null;

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  /**
   * @private
   * @type {goog.events.Key}
   */
  this.postComposeListenerKey_ = null;

  /**
   * @private
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.feature.StyleFunction}
   */
  this.style_ = null;

  /**
   * @private
   * @type {ol.feature.StyleFunction|undefined}
   */
  this.styleFunction_ = goog.isDef(options.style) ?
      ol.feature.createStyleFunction(options.style) : undefined;

  if (goog.isDef(options.features)) {
    if (goog.isArray(options.features)) {
      this.setFeatures(new ol.Collection(goog.array.clone(options.features)));
    } else {
      goog.asserts.assertInstanceof(options.features, ol.Collection);
      this.setFeatures(options.features);
    }
  } else {
    this.setFeatures(new ol.Collection());
  }

  if (goog.isDef(options.map)) {
    this.setMap(options.map);
  }

};

/**
 * @param {ol.Feature} feature Feature.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.addFeature = function(feature) {
  this.features_.push(feature);
};

/**
 * @return {ol.Collection} Features collection.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.getFeatures = function() {
  return this.features_;
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesAdd_ =
    function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
      goog.events.listen(feature, goog.events.EventType.CHANGE,
      this.handleFeatureChange_, false, this);
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesRemove_ =
    function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  var key = goog.getUid(feature).toString();
  goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]);
  delete this.featureChangeListenerKeys_[key];
  this.render_();
};

/**
 * @param {ol.render.Event} event Event.
 * @private
 */
ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) {
  if (goog.isNull(this.features_)) {
    return;
  }
  var styleFunction = this.styleFunction_;
  if (!goog.isDef(styleFunction)) {
    styleFunction = ol.feature.defaultStyleFunction;
  }
  var resolution = event.frameState.view2DState.resolution;
  var vectorContext = event.vectorContext;
  var i, ii, styles;
  this.features_.forEach(function(feature) {
    styles = styleFunction(feature, resolution);
    if (!goog.isDefAndNotNull(styles)) {
      return;
    }
    ii = styles.length;
    for (i = 0; i < ii; ++i) {
      vectorContext.drawFeature(feature, styles[i]);
    }
  }, this);
};

/**
 * @param {ol.Feature} feature Feature.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.removeFeature = function(feature) {
  this.features_.remove(feature);
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.render_ = function() {
  if (!goog.isNull(this.map_)) {
    this.map_.render();
  }
};

/**
 * @param {ol.Collection} features Features collection.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.setFeatures = function(features) {
  if (!goog.isNull(this.featuresListenerKeys_)) {
    goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey);
    this.featuresListenerKeys_ = null;
  }
  if (!goog.isNull(this.featureChangeListenerKeys_)) {
    goog.array.forEach(
        goog.object.getValues(this.featureChangeListenerKeys_),
        goog.events.unlistenByKey);
    this.featureChangeListenerKeys_ = null;
  }
  this.features_ = features;
  if (!goog.isNull(features)) {
    this.featuresListenerKeys_ = [
      goog.events.listen(features, ol.CollectionEventType.ADD,
          this.handleFeaturesAdd_, false, this),
      goog.events.listen(features, ol.CollectionEventType.REMOVE,
          this.handleFeaturesRemove_, false, this)
    ];
    this.featureChangeListenerKeys_ = {};
    var featuresArray = features.getArray();
    var i, ii = featuresArray.length;
    var feature;
    for (i = 0; i < ii; ++i) {
      feature = featuresArray[i];
      this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
          goog.events.listen(feature, goog.events.EventType.CHANGE,
          this.handleFeatureChange_, false, this);
    }
  }
  this.render_();
};

/**
 * @param {ol.Map} map Map.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.setMap = function(map) {
  if (!goog.isNull(this.postComposeListenerKey_)) {
    goog.events.unlistenByKey(this.postComposeListenerKey_);
    this.postComposeListenerKey_ = null;
  }
  this.render_();
  this.map_ = map;
  if (!goog.isNull(map)) {
    this.postComposeListenerKey_ = goog.events.listen(
        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
        this);
    map.render();
  }
};

/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.feature.StyleFunction} style
 *     Overlay style.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.setStyle = function(style) {
  this.style_ = style;
  this.styleFunction_ = ol.feature.createStyleFunction(style);
  this.render_();
};

/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.feature.StyleFunction}
 *     Overlay style.
 */
ol.FeatureOverlay.prototype.getStyle = function() {
  return this.style_;
};

/**
 * Get the style function.
 * @return {ol.feature.StyleFunction|undefined} Style function.
 */
ol.FeatureOverlay.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};
<commit_msg>Remove unneeded function binding<commit_after>goog.provide('ol.FeatureOverlay');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.Collection');
goog.require('ol.CollectionEventType');
goog.require('ol.Feature');
goog.require('ol.feature');
goog.require('ol.render.EventType');

/**
 * @constructor
 * @param {olx.FeatureOverlayOptions=} opt_options Options.
 * @todo stability experimental
 */
ol.FeatureOverlay = function(opt_options) {

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * @private
   * @type {ol.Collection}
   */
  this.features_ = null;

  /**
   * @private
   * @type {Array.<goog.events.Key>}
   */
  this.featuresListenerKeys_ = null;

  /**
   * @private
   * @type {Object.<string, goog.events.Key>}
   */
  this.featureChangeListenerKeys_ = null;

  /**
   * @private
   * @type {ol.Map}
   */
  this.map_ = null;

  /**
   * @private
   * @type {goog.events.Key}
   */
  this.postComposeListenerKey_ = null;

  /**
   * @private
   * @type {ol.style.Style|Array.<ol.style.Style>|ol.feature.StyleFunction}
   */
  this.style_ = null;

  /**
   * @private
   * @type {ol.feature.StyleFunction|undefined}
   */
  this.styleFunction_ = goog.isDef(options.style) ?
      ol.feature.createStyleFunction(options.style) : undefined;

  if (goog.isDef(options.features)) {
    if (goog.isArray(options.features)) {
      this.setFeatures(new ol.Collection(goog.array.clone(options.features)));
    } else {
      goog.asserts.assertInstanceof(options.features, ol.Collection);
      this.setFeatures(options.features);
    }
  } else {
    this.setFeatures(new ol.Collection());
  }

  if (goog.isDef(options.map)) {
    this.setMap(options.map);
  }

};

/**
 * @param {ol.Feature} feature Feature.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.addFeature = function(feature) {
  this.features_.push(feature);
};

/**
 * @return {ol.Collection} Features collection.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.getFeatures = function() {
  return this.features_;
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.handleFeatureChange_ = function() {
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesAdd_ =
    function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
      goog.events.listen(feature, goog.events.EventType.CHANGE,
      this.handleFeatureChange_, false, this);
  this.render_();
};

/**
 * @private
 * @param {ol.CollectionEvent} collectionEvent Collection event.
 */
ol.FeatureOverlay.prototype.handleFeaturesRemove_ =
    function(collectionEvent) {
  goog.asserts.assert(!goog.isNull(this.featureChangeListenerKeys_));
  var feature = /** @type {ol.Feature} */ (collectionEvent.element);
  var key = goog.getUid(feature).toString();
  goog.events.unlistenByKey(this.featureChangeListenerKeys_[key]);
  delete this.featureChangeListenerKeys_[key];
  this.render_();
};

/**
 * @param {ol.render.Event} event Event.
 * @private
 */
ol.FeatureOverlay.prototype.handleMapPostCompose_ = function(event) {
  if (goog.isNull(this.features_)) {
    return;
  }
  var styleFunction = this.styleFunction_;
  if (!goog.isDef(styleFunction)) {
    styleFunction = ol.feature.defaultStyleFunction;
  }
  var resolution = event.frameState.view2DState.resolution;
  var vectorContext = event.vectorContext;
  var i, ii, styles;
  this.features_.forEach(function(feature) {
    styles = styleFunction(feature, resolution);
    if (!goog.isDefAndNotNull(styles)) {
      return;
    }
    ii = styles.length;
    for (i = 0; i < ii; ++i) {
      vectorContext.drawFeature(feature, styles[i]);
    }
  });
};

/**
 * @param {ol.Feature} feature Feature.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.removeFeature = function(feature) {
  this.features_.remove(feature);
};

/**
 * @private
 */
ol.FeatureOverlay.prototype.render_ = function() {
  if (!goog.isNull(this.map_)) {
    this.map_.render();
  }
};

/**
 * @param {ol.Collection} features Features collection.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.setFeatures = function(features) {
  if (!goog.isNull(this.featuresListenerKeys_)) {
    goog.array.forEach(this.featuresListenerKeys_, goog.events.unlistenByKey);
    this.featuresListenerKeys_ = null;
  }
  if (!goog.isNull(this.featureChangeListenerKeys_)) {
    goog.array.forEach(
        goog.object.getValues(this.featureChangeListenerKeys_),
        goog.events.unlistenByKey);
    this.featureChangeListenerKeys_ = null;
  }
  this.features_ = features;
  if (!goog.isNull(features)) {
    this.featuresListenerKeys_ = [
      goog.events.listen(features, ol.CollectionEventType.ADD,
          this.handleFeaturesAdd_, false, this),
      goog.events.listen(features, ol.CollectionEventType.REMOVE,
          this.handleFeaturesRemove_, false, this)
    ];
    this.featureChangeListenerKeys_ = {};
    var featuresArray = features.getArray();
    var i, ii = featuresArray.length;
    var feature;
    for (i = 0; i < ii; ++i) {
      feature = featuresArray[i];
      this.featureChangeListenerKeys_[goog.getUid(feature).toString()] =
          goog.events.listen(feature, goog.events.EventType.CHANGE,
          this.handleFeatureChange_, false, this);
    }
  }
  this.render_();
};

/**
 * @param {ol.Map} map Map.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.setMap = function(map) {
  if (!goog.isNull(this.postComposeListenerKey_)) {
    goog.events.unlistenByKey(this.postComposeListenerKey_);
    this.postComposeListenerKey_ = null;
  }
  this.render_();
  this.map_ = map;
  if (!goog.isNull(map)) {
    this.postComposeListenerKey_ = goog.events.listen(
        map, ol.render.EventType.POSTCOMPOSE, this.handleMapPostCompose_, false,
        this);
    map.render();
  }
};

/**
 * Set the style for features.  This can be a single style object, an array
 * of styles, or a function that takes a feature and resolution and returns
 * an array of styles.
 * @param {ol.style.Style|Array.<ol.style.Style>|ol.feature.StyleFunction} style
 *     Overlay style.
 * @todo stability experimental
 */
ol.FeatureOverlay.prototype.setStyle = function(style) {
  this.style_ = style;
  this.styleFunction_ = ol.feature.createStyleFunction(style);
  this.render_();
};

/**
 * Get the style for features.  This returns whatever was passed to the `style`
 * option at construction or to the `setStyle` method.
 * @return {ol.style.Style|Array.<ol.style.Style>|ol.feature.StyleFunction}
 *     Overlay style.
 */
ol.FeatureOverlay.prototype.getStyle = function() {
  return this.style_;
};

/**
 * Get the style function.
 * @return {ol.feature.StyleFunction|undefined} Style function.
 */
ol.FeatureOverlay.prototype.getStyleFunction = function() {
  return this.styleFunction_;
};
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var permuterFactory = require('../index').permuterFactory;

describe('permuter', function () {
  it('should work stand-alone', function () {
    var permuter = permuterFactory(['a','b']);
    expect(permuter(['x'])).to.eql([
        ['x', 'a'],
        ['x', 'b']
      ]);
  });
  it('should work with reduce', function () {
    var permuter = permuterFactory(['a', 'b']);
    expect([['x']].reduce(permuter, [])).to.eql([
        ['x', 'a'],
        ['x', 'b']
      ]);
  });
});<commit_msg>Add example for swedish grandparent names<commit_after>var expect = require('chai').expect;
var permuterFactory = require('../index').permuterFactory;

describe('permuter', function () {
  it('should work stand-alone', function () {
    var permuter = permuterFactory(['a','b']);
    expect(permuter(['x'])).to.eql([
        ['x', 'a'],
        ['x', 'b']
      ]);
  });
  it('should work with reduce', function () {
    var permuter = permuterFactory(['a', 'b']);
    expect([['x']].reduce(permuter, [])).to.eql([
        ['x', 'a'],
        ['x', 'b']
      ]);
  });
  it('can be used to compute swedish ancestor names', function () {
    var parents = ['mor', 'far'];
    var permuter = permuterFactory(parents);

    expect([['']]
      .reduce(permuter, [])
      .reduce(permuter, [])
      .map(function (cur) {return cur.join('');})).to.eql([
        'mormor',
        'morfar',
        'farmor',
        'farfar'
      ]);
  });
});<|endoftext|>


<commit_before>//General vars
var layersTypes = ["fc","dropout","flat","reshape","conv1","conv2","conv3","maxp1","maxp2","maxp3","avep1","avep2","avep3","rnn","gru","lstm","wembed"];
var kerasLayers = ["Dense","Dropout","Flatten","Reshape","Conv1D","Conv2D","Conv3D","MaxPooling1D","MaxPooling2D","MaxPooling3D","AveragePooling1D","AveragePooling2D","AveragePooling3D","SimpleRNN","GRU","LSTM","Embedding"];
var layers =  new Array();
var layersOutput = new Array();
var layersParameters = new Array();

function getPar( str, par){
	var pos = str.indexOf(par);
	if(pos == -1)return -1;
	str = str.slice(str.indexOf(par)+par.length,str.length);
	if(str.indexOf(",")){
		str=str.slice(0,str.indexOf(","))
	}else{
		str=str.slice(0,str.indexOf(")"))
	}

	return str
}

function getArrayPar( str, par){
	var pos = str.indexOf(par);
	if(pos == -1)return -1;
	str = str.slice(str.indexOf(par)+par.length,str.length);
	str = str.slice(str.indexOf("("),str.indexOf(")")+1)
	return str
}

$("#btn").click(function(){
  layers = ["input"];
  layersOutput = new Array();
  layersParameters = new Array();
  var p = $(".enter_stuff").val();
  var e = $('input[type="radio"]:checked').val();
  switch(e){
    case "plaintxt":
    getText(p);
    break;
    case "keras":
    getKeras(p);
    break;
    case "tflearn":
    getTFlearn(p);
    break;
  }
  console.log(layers)
  console.log(layersOutput)
  console.log(layersParameters)

  buildChart(layers,layersOutput,layersParameters);
});

//Function to parse keras syntax
function getKeras(inputText){
  var lines = inputText.split("\n");
  for(var i = 0;i != lines.length;i++){
    lines[i] = lines[i].split(" ").join("") //OK

    if(lines[i][0] != '#'){
    for(var t = 0;t != layersTypes.length;t++){
      if(lines[i].indexOf(kerasLayers[t]) != -1){
        layers.push(layersTypes[t]);

		//Define input shape
		if(layers.length == 2){
			if(lines[i].indexOf("input_dim") != -1){
				ris = getPar(lines[i],"input_dim=")
				layersOutput.push(parseFloat(ris))
			}
			else if(lines[i].indexOf("input_shape") != -1){
				ris = getArrayPar(lines[i],"input_shape=")
				layersOutput.push(ris)
			}
			else{
				layersOutput.push(-4);
			}
		}

		//Flatten handler
        if(kerasLayers[t] == "Flatten") {
            layersOutput.push(-1)
        }

		//Reshape handler
        else if(kerasLayers[t] == "Reshape"){
            layersOutput.push(lines[i].slice(lines[i].indexOf("Reshape(")+8,lines[i].indexOf(")")+1))

        }

		//Convolutional handler
		else if(kerasLayers[t].indexOf("Conv") != -1){
			if(lines[i].indexOf("filters=") != -1){
				ris = getPar(lines[i],"filters=")
				layersOutput.push(parseFloat(ris))

			}
			else{
				ris = getPar(lines[i],kerasLayers[t]+"(")
				layersOutput.push(parseFloat(ris))
			}

			//Kernel size
			if(lines[i].indexOf("kernel_size") != -1){
				ris = getArrayPar(lines[i],"kernel_size=")
				layersParameters.push(ris)
			}
			else{
				cannelloni = lines[i].slice(lines[i].indexOf(kerasLayers[t]+"(")+(kerasLayers[t].length+1),lines[i].length)
				ris = cannelloni.slice(cannelloni[i].indexOf("("),cannelloni[i].indexOf(")")+1)
				console.log(ris)
			}
		}
		//Pooling layer
		else if(kerasLayers[t].indexOf("Pool") != -1){
			ris = getArrayPar(lines[i],"pool_size=")
			layersOutput.push(ris)
		}
		//All the other layers
        else{
            var str = lines[i].split(",");
			if(lines[i].indexOf("units") != -1){
					ris = getPar(lines[i],"units=")
					ris = parseFloat(ris)
					layersOutput.push(ris)
			}
			else{
				layersOutput.push(parseFloat(lines[i].slice(lines[i].indexOf(kerasLayers[t]+"(")+(kerasLayers[t].length+1),lines[i].indexOf(",")).replace(")",""))) // )
			}
        }

		//If there are no special paramaters set to null
		if(layers.length != layersParameters.length){
			layersParameters.push(null)
		}
		break;
    }
  }
  }
}
}

//Function To parse tflearn syntax
function getTFlearn(inputText){
var tfLayers = ["fully_connected","dropout","flatten","reshape","conv_1d","conv_2d","conv_3d","max_pool_1d","max_pool_2d","max_pool_3d","avg_pool_1d","avg_pool_2d","avg_pool_3d"];
  var lines = inputText.split("\n");
  for(var i = 0;i != lines.length;i++){
    for(var t = 0;t != tfLayers.length;t++){
      if(lines[i].indexOf(tfLayers[t]) != -1){
        layers.push(layersTypes[t]);
      }
    }
  }
}

//Funtction to implement text like description(in the future...)
function getText(inputText){
  var lines = inputText.split("\n");

}

<commit_msg>Update main.js<commit_after>//General vars
var layersTypes = ["fc","dropout","flat","reshape","conv1","conv2","conv3","maxp1","maxp2","maxp3","avep1","avep2","avep3","rnn","gru","lstm","wembed"];
var kerasLayers = ["Dense","Dropout","Flatten","Reshape","Conv1D","Conv2D","Conv3D","MaxPooling1D","MaxPooling2D","MaxPooling3D","AveragePooling1D","AveragePooling2D","AveragePooling3D","SimpleRNN","GRU","LSTM","Embedding"];
var layers =  new Array();
var layersOutput = new Array();
var layersParameters = new Array();

function getPar( str, par){
	var pos = str.indexOf(par);
	if(pos == -1)return -1;
	str = str.slice(str.indexOf(par)+par.length,str.length);
	if(str.indexOf(",")){
		str=str.slice(0,str.indexOf(","))
	}else{
		str=str.slice(0,str.indexOf(")"))
	}

	return str
}

function getArrayPar( str, par){
	var pos = str.indexOf(par);
	if(pos == -1)return -1;
	str = str.slice(str.indexOf(par)+par.length,str.length);
	str = str.slice(str.indexOf("("),str.indexOf(")")+1)
	return str
}

$("#btn").click(function(){
  layers = ["input"];
  layersOutput = new Array();
  layersParameters = new Array();
  var p = $(".enter_stuff").val();
  var e = $('input[type="radio"]:checked').val();
  switch(e){
    case "plaintxt":
    getText(p);
    break;
    case "keras":
    getKeras(p);
    break;
    case "tflearn":
    getTFlearn(p);
    break;
  }
  console.log(layers)
  console.log(layersOutput)
  console.log(layersParameters)

  buildChart(layers,layersOutput,layersParameters);
});

//Function to parse keras syntax
function getKeras(inputText){
  var lines = inputText.split("\n");
  for(var i = 0;i != lines.length;i++){
    lines[i] = lines[i].split(" ").join("") //OK

    if(lines[i][0] != '#'){
    for(var t = 0;t != layersTypes.length;t++){
      if(lines[i].indexOf(kerasLayers[t]) != -1){
        layers.push(layersTypes[t]);

		//Define input shape
		if(layers.length == 2){
			if(lines[i].indexOf("input_dim") != -1){
				ris = getPar(lines[i],"input_dim=")
				layersOutput.push(parseFloat(ris))
			}
			else if(lines[i].indexOf("input_shape") != -1){
				ris = getArrayPar(lines[i],"input_shape=")
				layersOutput.push(ris)
			}
			else{
				layersOutput.push(-4);
			}
		}

		//Flatten handler
        if(kerasLayers[t] == "Flatten") {
            layersOutput.push(-1)
        }

	//Reshape handler
        else if(kerasLayers[t] == "Reshape"){
            layersOutput.push(lines[i].slice(lines[i].indexOf("Reshape(")+8,lines[i].indexOf(")")+1))

        }

		//Convolutional handler
		else if(kerasLayers[t].indexOf("Conv") != -1){
			if(lines[i].indexOf("filters=") != -1){
				ris = getPar(lines[i],"filters=")
				layersOutput.push(parseFloat(ris))

			}
			else{
				ris = getPar(lines[i],kerasLayers[t]+"(")
				layersOutput.push(parseFloat(ris))
			}

			//Kernel size
			if(lines[i].indexOf("kernel_size") != -1){
				ris = getArrayPar(lines[i],"kernel_size=")
				layersParameters.push(ris)
			}
			else{
				cannelloni = lines[i].slice(lines[i].indexOf(kerasLayers[t]+"(")+(kerasLayers[t].length+1),lines[i].length)
				ris = cannelloni.slice(cannelloni[i].indexOf("("),cannelloni[i].indexOf(")")+1)
				console.log(ris)
			}
		}
		//Pooling layer
		else if(kerasLayers[t].indexOf("Pool") != -1){
			ris = getArrayPar(lines[i],"pool_size=")
			layersOutput.push(ris)
		}
		//All the other layers
        else{
            var str = lines[i].split(",");
			if(lines[i].indexOf("units") != -1){
					ris = getPar(lines[i],"units=")
					ris = parseFloat(ris)
					layersOutput.push(ris)
			}
			else{
				layersOutput.push(parseFloat(lines[i].slice(lines[i].indexOf(kerasLayers[t]+"(")+(kerasLayers[t].length+1),lines[i].indexOf(",")).replace(")",""))) // )
			}
        }

		//If there are no special paramaters set to null
		if(layers.length != layersParameters.length){
			layersParameters.push(null)
		}
		break;
    }
  }
  }
}
}

//Function To parse tflearn syntax
function getTFlearn(inputText){
var tfLayers = ["fully_connected","dropout","flatten","reshape","conv_1d","conv_2d","conv_3d","max_pool_1d","max_pool_2d","max_pool_3d","avg_pool_1d","avg_pool_2d","avg_pool_3d"];
  var lines = inputText.split("\n");
  for(var i = 0;i != lines.length;i++){
    for(var t = 0;t != tfLayers.length;t++){
      if(lines[i].indexOf(tfLayers[t]) != -1){
        layers.push(layersTypes[t]);
      }
    }
  }
}

//Funtction to implement text like description(in the future...)
function getText(inputText){
  var lines = inputText.split("\n");

}

<|endoftext|>


<commit_before><commit_msg>Add get timezone offset helper<commit_after><|endoftext|>


<commit_before>'use strict';

var c = process.binding('constants')
  , queue = Object.create(null)
  , fs = process.binding('fs')
  , sfs = module.exports;

//
// Generate some name defaults for the flags.
//
c = 'O_APPEND,O_CREAT,O_EXCL,O_RDONLY,O_RDWR,O_SYNC,O_TRUNC,O_WRONLY'.split(',')
.reduce(function defaults(memo, flag) {
  memo[flag] = c[flag] || 0;
  return memo;
}, Object.create(null));

/* jshint: ignore:start */
sfs.flags = Object.create(null);

sfs.flags['r']    = c.O_RDONLY;
sfs.flags['rs']   = // Fall through, used for alias.
sfs.flags['sr']   = c.O_RDONLY | c.O_SYNC;
sfs.flags['r+']   = c.O_RDWR;
sfs.flags['rs+']  = // Fall through, used for alias.
sfs.flags['sr+']  = c.O_RDWR | c.O_SYNC;

sfs.flags['w']    = c.O_TRUNC | c.O_CREAT | c.O_WRONLY;
sfs.flags['wx']   = // Fall through, used for alias.
sfs.flags['xw']   = c.O_TRUNC | c.O_CREAT | c.O_WRONLY | c.O_EXCL;
sfs.flags['w+']   = c.O_TRUNC | c.O_CREAT | c.O_RDWR;
sfs.flags['wx+']  = // Fall through, used for alias.
sfs.flags['xw+']  = c.O_TRUNC | c.O_CREAT | c.O_RDWR | c.O_EXCL;

sfs.flags['a']    = c.O_APPEND | c.O_CREAT | c.O_WRONLY;
sfs.flags['ax']   = // Fall through, used for alias.
sfs.flags['xa']   = c.O_APPEND | c.O_CREAT | c.O_WRONLY | c.O_EXCL;
sfs.flags['a+']   = c.O_APPEND | c.O_CREAT | c.O_RDWR;
sfs.flags['ax+']  = // Fall through, used for alias.
sfs.flags['xa+']  = c.O_APPEND | c.O_CREAT | c.O_RDWR | c.O_EXCL;
/* jshint: ignore:end */

/**
 * Checks if a file exists on the current hard drive.
 *
 * @async
 * @param {String} path Path to a thing that needs to exist.
 * @param {Function} fn Completion callback.
 * @api private
 */
sfs.exists = function exists(path, fn) {
  if (running(exists, path, fn)) return;

  fs.stat(path, function done(err) {
    complete(exists, path, undefined, !err);
  });
};

/**
  * Checks if a file exists on the current hard drive.
  *
  * @sync
  * @param {String} path Path to a thing that needs to exist.
  * @api private
 */
sfs.existsSync = function exists(path) {
  return !trying(function stat() {
    fs.stat(path);
  });
};

/**
 * Open a file descriptor.
 *
 * @async
 * @param {String} path Path to the file we're trying to open.
 * @param {String} flags Flags for opening.
 * @param {Number} mode Opening mode.
 * @param {Function} fn Completion callback.
 * @api private
 */
sfs.open = function open(path, flags, mode, fn) {
  flags = sfs.flags[flags] || flags;
  fs.open(path, flags, +mode || 438, fn);
};

/**
 * Open a file descriptor.
 *
 * @sync
 * @param {String} path Path to the file we're trying to open.
 * @param {String} flags Flags for opening.
 * @param {Number} mode Opening mode.
 * @api private
 */
sfs.openSync = function openSync(path, flags, mode) {
  flags = sfs.flags[flags] || flags;
  return fs.open(path, flags, +mode || 438);
};

/**
 * Close a given file descriptor. We just reference the binding directly as
 * there is no need for extra function wrapping as the arguments map 1 on 1. For
 * the synchronous version, just omit the callback argument.
 *
 * @async/@sync
 * @param {FileDescriptor} fd The file descriptor that needs to be closed.
 * @param {Function} fn Completion callback.
 * @api private
 */
sfs.closeSync = sfs.close = fs.close;

/**
 * Small wrapper for try/catch blocks to prevent a whole function to be
 * de-optimized.
 *
 * @param {Function} fn Function to attempt to execute.
 * @returns {Error}
 * @api public
 */
function trying(fn) {
  try { return fn(); }
  catch (e) { return e; }
}

/**
 * Check if a certain operation is already running in our processing queue. It's
 * pointless. Because it's pointless to concurrently run exactly the same task.
 * We do not require a high level of uniqueness within this module.
 *
 * @param {Function} method Reference to the function that needs the queue.
 * @param {String} path Location of a thing.
 * @param {Function} fn Completion callback.
 * @api private
 */
function running(method, path, fn) {
  var id = method.sfs +'@'+ path;

  if (!queue[id]) {
    queue[id] = [fn];
    return false;
  }

  queue[id].push(fn);
  return true;
}

/**
 * A queued operation has been completed, call all the callbacks with the
 * results or error.
 *
 * @param {Function} method Reference to the function that needs the queue.
 * @param {String} path Location of a thing.
 * @param {Error} err Optional error argument for callback.
 * @param {Mixed} data Result of the async operation.
 * @api private
 */
function complete(method, path, err, data) {
  var id = method.sfs +'@'+ path
    , fns = queue[id]
    , l = fns.length
    , i = 0;

  //
  // Delete the queue before we call the callbacks. This prevents users from
  // adding new calls to the queue that we're currently processing.
  //
  delete queue[id];

  for (; i < l; i++) {
    fns[i](err, data);
  }
}

//
// Add a unique id for each of the supreme file system methods so we can use it
// check in our running queue and execute callbacks at once.
//
Object.keys(sfs).forEach(function tag(method) {
  if ('function' !== typeof sfs[method]) return;

  sfs[method].sfs = this.createHash('md5').update(
    sfs[method].toString()  // Use the function body as content for the hash
  ).digest('hex');
}, require('crypto'));
<commit_msg>[fix] Force API compatiblity<commit_after>'use strict';

var c = process.binding('constants')
  , queue = Object.create(null)
  , fs = process.binding('fs')
  , sfs = module.exports;

//
// Generate some name defaults for the flags.
//
c = 'O_APPEND,O_CREAT,O_EXCL,O_RDONLY,O_RDWR,O_SYNC,O_TRUNC,O_WRONLY'.split(',')
.reduce(function defaults(memo, flag) {
  memo[flag] = c[flag] || 0;
  return memo;
}, Object.create(null));

/* jshint: ignore:start */
sfs.flags = Object.create(null);

sfs.flags['r']    = c.O_RDONLY;
sfs.flags['rs']   = // Fall through, used for alias.
sfs.flags['sr']   = c.O_RDONLY | c.O_SYNC;
sfs.flags['r+']   = c.O_RDWR;
sfs.flags['rs+']  = // Fall through, used for alias.
sfs.flags['sr+']  = c.O_RDWR | c.O_SYNC;

sfs.flags['w']    = c.O_TRUNC | c.O_CREAT | c.O_WRONLY;
sfs.flags['wx']   = // Fall through, used for alias.
sfs.flags['xw']   = c.O_TRUNC | c.O_CREAT | c.O_WRONLY | c.O_EXCL;
sfs.flags['w+']   = c.O_TRUNC | c.O_CREAT | c.O_RDWR;
sfs.flags['wx+']  = // Fall through, used for alias.
sfs.flags['xw+']  = c.O_TRUNC | c.O_CREAT | c.O_RDWR | c.O_EXCL;

sfs.flags['a']    = c.O_APPEND | c.O_CREAT | c.O_WRONLY;
sfs.flags['ax']   = // Fall through, used for alias.
sfs.flags['xa']   = c.O_APPEND | c.O_CREAT | c.O_WRONLY | c.O_EXCL;
sfs.flags['a+']   = c.O_APPEND | c.O_CREAT | c.O_RDWR;
sfs.flags['ax+']  = // Fall through, used for alias.
sfs.flags['xa+']  = c.O_APPEND | c.O_CREAT | c.O_RDWR | c.O_EXCL;
/* jshint: ignore:end */

/**
 * Checks if a file exists on the current hard drive.
 *
 * @async
 * @param {String} path Path to a thing that needs to exist.
 * @param {Function} fn Completion callback.
 * @api private
 */
sfs.exists = function exists(path, fn) {
  if (running(exists, path, fn)) return;

  fs.stat(path, function done(err) {
    complete(exists, path, undefined, !err);
  });
};

/**
  * Checks if a file exists on the current hard drive.
  *
  * @sync
  * @param {String} path Path to a thing that needs to exist.
  * @api private
 */
sfs.existsSync = function exists(path) {
  return !trying(function stat() {
    fs.stat(path);
  });
};

/**
 * Open a file descriptor.
 *
 * @async
 * @param {String} path Path to the file we're trying to open.
 * @param {String} flags Flags for opening.
 * @param {Number} mode Opening mode.
 * @param {Function} fn Completion callback.
 * @api private
 */
sfs.open = function open(path, flags, mode, fn) {
  flags = sfs.flags[flags] || flags;
  fs.open(path, flags, +mode || 438, fn);
};

/**
 * Open a file descriptor.
 *
 * @sync
 * @param {String} path Path to the file we're trying to open.
 * @param {String} flags Flags for opening.
 * @param {Number} mode Opening mode.
 * @api private
 */
sfs.openSync = function openSync(path, flags, mode) {
  flags = sfs.flags[flags] || flags;
  return fs.open(path, flags, +mode || 438);
};

/**
 * Close a given file descriptor. We just reference the binding directly as
 * there is no need for extra function wrapping as the arguments map 1 on 1. For
 * the synchronous version, just omit the callback argument.
 *
 * @async/@sync
 * @param {FileDescriptor} fd The file descriptor that needs to be closed.
 * @param {Function} fn Completion callback.
 * @api private
 */
sfs.closeSync = sfs.close = fs.close;

/**
 * Small wrapper for try/catch blocks to prevent a whole function to be
 * de-optimized.
 *
 * @param {Function} fn Function to attempt to execute.
 * @returns {Error}
 * @api public
 */
function trying(fn) {
  try { return fn(); }
  catch (e) { return e; }
}

/**
 * Check if a certain operation is already running in our processing queue. It's
 * pointless. Because it's pointless to concurrently run exactly the same task.
 * We do not require a high level of uniqueness within this module.
 *
 * @param {Function} method Reference to the function that needs the queue.
 * @param {String} path Location of a thing.
 * @param {Function} fn Completion callback.
 * @api private
 */
function running(method, path, fn) {
  var id = method.sfs +'@'+ path;

  if (!queue[id]) {
    queue[id] = [fn];
    return false;
  }

  queue[id].push(fn);
  return true;
}

/**
 * A queued operation has been completed, call all the callbacks with the
 * results or error.
 *
 * @param {Function} method Reference to the function that needs the queue.
 * @param {String} path Location of a thing.
 * @param {Error} err Optional error argument for callback.
 * @param {Mixed} data Result of the async operation.
 * @api private
 */
function complete(method, path, err, data) {
  var id = method.sfs +'@'+ path
    , fns = queue[id]
    , l = fns.length
    , i = 0;

  //
  // Delete the queue before we call the callbacks. This prevents users from
  // adding new calls to the queue that we're currently processing.
  //
  delete queue[id];

  for (; i < l; i++) {
    fns[i](err, data);
  }
}

//
// Add a unique id for each of the supreme file system methods so we can use it
// check in our running queue and execute callbacks at once.
//
Object.keys(sfs).forEach(function tag(method) {
  if ('function' !== typeof sfs[method]) return;

  sfs[method].sfs = this.createHash('md5').update(
    sfs[method].toString()  // Use the function body as content for the hash
  ).digest('hex');
}, require('crypto'));

//
// Now that we've compiled unique hash methods on every single `sfs` tuned
// function we can introduce all potential missing API's from the node's fs
// module to ensure that we we have full API compatibility.
//
Object.keys(require('fs')).forEach(function each(key) {
  if (key in sfs) return;
  sfs[key] = require('fs')[key];
});
<|endoftext|>


<commit_before>function (user, context, callback) {
  const CHANGEAPI_TIMEOUT = 14000; // milliseconds
  const MIN_LOGINS_TO_ASSUME_EXISTENCE_IN_CIS = 6;
  const PERSONAPI_BEARER_TOKEN_REFRESH_AGE = 64800; // 18 hours
  const PERSONAPI_TIMEOUT = 5000;  // milliseconds
  const PUBLISHER_NAME = 'access_provider';
  const WHITELISTED_CONNECTIONS = ['email', 'firefoxaccounts', 'github', 'google-oauth2'];

  // if we don't have the configuration variables we need, bail
  // note that this requires the "PersonAPI - Auth0" application configured with the following scopes:
  // classification: public, display: none, display: public, write
  if (!configuration.changeapi_auth0_private_key ||
      !configuration.changeapi_null_profile ||
      !configuration.changeapi_url ||
      !configuration.personapi_client_id ||
      !configuration.personapi_client_secret ||
      !configuration.personapi_url) {
    console.log('Error: Unable to find PersonAPI and/or ChangeAPI configuration');
    return callback(null, user, context);
  }

  // the `personapi_url` and `personapi_audience` variables have historically been configured incorrectly,
  // so we don't continue onward unless they have been fixed, e.g.:
  // personapi_audience: api.dev.sso.allizom.org / api.sso.mozilla.com
  // personapi_url: https://person.api.dev.sso.allizom.org / https://person.api.sso.mozilla.com
  if (configuration.personapi_audience.includes('https') || configuration.personapi_url.includes('/v')) {
    console.log('Error: PersonAPI configured incorrectly');
    return callback(null, user, context);
  }

  // We can only provision users that have certain connection strategies
  if (!WHITELISTED_CONNECTIONS.includes(context.connectionStrategy)) {
    return callback(null, user, context);
  }

  // if you're explicitly flagged as existing in CIS, then we don't need to continue onward
  if (user.user_metadata && user.user_metadata.existsInCIS) {
    return callback(null, user, context);
  }

  // if you've logged in several times before, we're going to assume you exist, to speed
  // this function along. Once `existsInCIS` is reliable, we can probably remove this check
  if (context.stats && context.stats.loginsCount && context.stats.loginsCount >= MIN_LOGINS_TO_ASSUME_EXISTENCE_IN_CIS) {
    return callback(null, user, context);
  }

  // we'll need the node-fetch module, to add support for timeouts
  const fetch = require('node-fetch@2.6.0');

  // we also need to decode the private key from base64 into a PEM format that `jsonwebtoken` understands
  const privateKey = Buffer.from(configuration.changeapi_auth0_private_key, 'base64');

  const getBearerToken = async () => {
    // if we have the bearer token stored, we don't need to fetch it again
    if (global.personapi_bearer_token &&
        global.personapi_bearer_token_creation_time &&
        Date.now() - global.personapi_bearer_token_creation_time < PERSONAPI_BEARER_TOKEN_REFRESH_AGE) {
      return global.personapi_bearer_token;
    }

    console.log('Retrieving bearer token to create new user in CIS');

    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      timeout: PERSONAPI_TIMEOUT,
      body: JSON.stringify({
        audience: configuration.personapi_audience,
        client_id: configuration.personapi_client_id,
        client_secret: configuration.personapi_client_secret,
        grant_type: 'client_credentials',
      })
    };

    return fetch(configuration.personapi_oauth_url, options)
      .then(response => {
        return response.json();
      })
      .then(data => {
        // store the bearer token in the global object, so it's not constantly retrieved
        global.personapi_bearer_token = data.access_token;
        global.personapi_bearer_token_creation_time = Date.now();

        console.log(`Successfully retrieved bearer token from Auth0`);
        return global.personapi_bearer_token;
      })
      .catch(() => {
        throw Error('Unable to retrieve bearer token from Auth0');
      });
  };

  const createPersonProfile = async () => {
    console.log(`Generating CIS profile for ${user.user_id}`);

    let now = new Date();
    now = now.toISOString();

    // load the user skeleton, as generated by:
    // base64.b64encode(json.dumps(requests.get('https://raw.githubusercontent.com/mozilla-iam/cis/master/python-modules/cis_profile/cis_profile/data/user_profile_null.json').json(), separators=(',', ':')).encode('ascii'))
    const profile = JSON.parse(Buffer.from(configuration.changeapi_null_profile, 'base64'));

    // update attributes in the skeleton
    // normally we shouldn't need to change anything but the values, but this is manually doing it because
    // I have no idea if the skeleton will ever change underneath me
    profile.active.metadata.last_modified = now;
    profile.active.signature.publisher.name = PUBLISHER_NAME;
    profile.active.value = true;

    profile.first_name.metadata.display = 'private';
    profile.first_name.metadata.last_modified = now;
    profile.first_name.signature.publisher.name = PUBLISHER_NAME;
    profile.first_name.value = user.given_name ? user.given_name : ' ';

    profile.last_name.metadata.display = 'private';
    profile.last_name.metadata.last_modified = now;
    profile.last_name.signature.publisher.name = PUBLISHER_NAME;
    profile.last_name.value = user.family_name ? user.family_name : ' ';

    profile.primary_email.metadata.last_modified = now;
    profile.primary_email.signature.publisher.name = PUBLISHER_NAME;
    profile.primary_email.value = user.email;

    profile.user_id.metadata.last_modified = now;
    profile.user_id.signature.publisher.name = PUBLISHER_NAME;
    profile.user_id.value = user.user_id;

    // now we need to go and update the identities values; this is based on the logic here:
    // https://github.com/mozilla-iam/cis/blob/master/python-modules/cis_publisher/cis_publisher/auth0.py
    // which may or may not be correct, I dunno
    for (let key in user.identities) {
      const identity = user.identities[key];
      // ignore a provider if it's not whitelisted
      if (!WHITELISTED_CONNECTIONS.includes(identity.connection)) {
        continue;
      }

      if (identity.provider === 'github') {
        profile.identities.github_id_v3.metadata.display = 'private';
        profile.identities.github_id_v3.metadata.last_modified = now;
        profile.identities.github_id_v3.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.github_id_v3.value = identity.user_id;

        if (user.nickname) {
          profile.usernames.metadata.display = 'private';
          profile.usernames.signature.publisher.name = PUBLISHER_NAME;
          profile.usernames.value = {"HACK#GITHUB": user.nickname};
        }

        if (identity.profileData) {
          // I could never seem to find a user that met this condition
          profile.identities.github_id_v4.metadata.display = 'private';
          profile.identities.github_id_v4.metadata.last_modified = now;
          profile.identities.github_id_v4.signature.publisher.name = PUBLISHER_NAME;
          profile.identities.github_id_v4.value = identity.profileData.node_id;

          profile.identities.github_primary_email.metadata.display = 'private';
          profile.identities.github_primary_email.metadata.last_modified = now;
          profile.identities.github_primary_email.metadata.verified = identity.profileData.email_verified === true ? true : false;
          profile.identities.github_primary_email.signature.publisher.name = PUBLISHER_NAME;
          profile.identities.github_primary_email.value = identity.profileData.email;
        }
      }

      else if (identity.provider === 'google-oauth2') {
        profile.identities.google_oauth2_id.metadata.display = 'private';
        profile.identities.google_oauth2_id.metadata.last_modified = now;
        profile.identities.google_oauth2_id.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.google_oauth2_id.value = identity.user_id;

        profile.identities.google_primary_email.metadata.display = 'private';
        profile.identities.google_primary_email.metadata.last_modified = now;
        profile.identities.google_primary_email.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.google_primary_email.value = user.email;
      }

      else if (identity.connection === 'firefoxaccounts' && identity.provider === 'oauth2') {
        profile.identities.firefox_accounts_id.metadata.display = 'private';
        profile.identities.firefox_accounts_id.metadata.last_modified = now;
        profile.identities.firefox_accounts_id.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.firefox_accounts_id.value = identity.user_id;

        profile.identities.firefox_accounts_primary_email.metadata.display = 'private';
        profile.identities.firefox_accounts_primary_email.metadata.last_modified = now;
        profile.identities.firefox_accounts_primary_email.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.firefox_accounts_primary_email.value = user.email;
      }
    }

    // now, we need to sign every field and subfield
    signAll(profile);

    // turn this on only for debugging
    // console.log(`Generated profile:\n${JSON.stringify(profile, null, 2)}`);
    return profile;
  };

  const getPersonProfile = async () => {
    const bearer = await getBearerToken();
    const options = {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${bearer}`,
      },
      timeout: PERSONAPI_TIMEOUT,
    };
    const url = `${configuration.personapi_url}/v2/user/user_id/${encodeURI(user.user_id)}?active=any`;

    console.log(`Fetching person profile of ${user.user_id}`);

    return fetch(url, options)
      .then(response => response.json());
  };

  const postProfile = async profile => {
    console.log(`Posting profile for ${user.user_id} to ChangeAPI`);

    const bearer = await getBearerToken();
    const options = {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${bearer}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(profile),
      timeout: CHANGEAPI_TIMEOUT,
    };
    const url = `${configuration.changeapi_url}/v2/user?user_id=${encodeURI(user.user_id)}`;

    // POST the profile
    return fetch(url, options)
      .then(response => {
        return response.json();
      });
  };

  const signAll = profile => {
    // now we need to sign every attribute in the profile, if we're allowed to
    // otherwise, we will descend one level deep for sub attributes
    // this is super hacky and ugly and I hate it
    Object.values(profile).forEach(attr => {  // works because profile is mutable
      if (attr.constructor === Object && attr.signature) {
        signAttribute(attr);
      } else if (attr.constructor === Object && !attr.signature) {
        signAll(attr);  // descend deeper
      }
    });
  };

  const signAttribute = attr => {
    const jwt = require('jsonwebtoken');

    // we can only sign attributes that access_provider (e.g. auth0) is allowed to sign
    // we also ignore things that don't have a pre-existing signature field
    if (!attr.signature || attr.signature.publisher.name !== PUBLISHER_NAME) {
      return attr;
    }

    // we need to delete the existing signature and generate it anew
    delete(attr.signature);

    attr.signature = {
      additional: [{
        alg: 'RS256',
        name: null,
        typ: 'JWS',
        value: '',
      }],
      publisher: {
        alg: 'RS256',
        name: PUBLISHER_NAME,
        typ: 'JWS',
        value: jwt.sign(attr, privateKey, { algorithm: 'RS256', noTimestamp: true }),
      }
    };

    return attr;
  };

  // if we get this far, we need to 1) call the PersonAPI to check for existance, and 2) if the don't exist,
  // call the ChangeAPI to create them
  getPersonProfile()
    .then(profile => {
      if (Object.keys(profile).length !== 0) {
        throw Error(`Profile for ${user.user_id} already exists in PersonAPI`);
      } else {
        return createPersonProfile();
      }
    })
    .then(profile => postProfile(profile))
    .then(response => {
      if (response.constructor === Object && response.status_code === 200) {
        console.log(`Successfully created profile for ${user.user_id} in ChangeAPI`);
        return callback(null, user, context);
      } else {
        throw Error(`Unable to create profile for ${user.user_id} in ChangeAPI`);
      }
    })
    .catch(error => {
      console.log(`Error: ${error.message}`);
      return callback(null, user, context);
    });
}
<commit_msg>A bunch of fixes including:<commit_after>function (user, context, callback) {
  const CHANGEAPI_TIMEOUT = 14000; // milliseconds
  const MIN_LOGINS_TO_ASSUME_EXISTENCE_IN_CIS = 6;
  const PERSONAPI_BEARER_TOKEN_REFRESH_AGE = 64800; // 18 hours
  const PERSONAPI_TIMEOUT = 5000;  // milliseconds
  const PUBLISHER_NAME = 'access_provider';
  const WHITELISTED_CONNECTIONS = ['email', 'firefoxaccounts', 'github', 'google-oauth2'];

  // if we don't have the configuration variables we need, bail
  // note that this requires the "PersonAPI - Auth0" application configured with the following scopes:
  // classification: public, display: none, display: public, write
  if (!configuration.changeapi_auth0_private_key ||
      !configuration.changeapi_null_profile ||
      !configuration.changeapi_url ||
      !configuration.personapi_client_id ||
      !configuration.personapi_client_secret ||
      !configuration.personapi_url) {
    console.log('Error: Unable to find PersonAPI and/or ChangeAPI configuration');
    return callback(null, user, context);
  }

  // the `personapi_url` and `personapi_audience` variables have historically been configured incorrectly,
  // so we don't continue onward unless they have been fixed, e.g.:
  // personapi_audience: api.dev.sso.allizom.org / api.sso.mozilla.com
  // personapi_url: https://person.api.dev.sso.allizom.org / https://person.api.sso.mozilla.com
  if (configuration.personapi_audience.includes('https') || configuration.personapi_url.includes('/v')) {
    console.log('Error: PersonAPI configured incorrectly');
    return callback(null, user, context);
  }

  // We can only provision users that have certain connection strategies
  if (!WHITELISTED_CONNECTIONS.includes(context.connectionStrategy)) {
    return callback(null, user, context);
  }

  // if you're explicitly flagged as existing in CIS, then we don't need to continue onward
  if (user.user_metadata && user.user_metadata.existsInCIS) {
    return callback(null, user, context);
  }

  // if you've logged in several times before, we're going to assume you exist, to speed
  // this function along. Once `existsInCIS` is reliable, we can probably remove this check
  if (context.stats && context.stats.loginsCount && context.stats.loginsCount >= MIN_LOGINS_TO_ASSUME_EXISTENCE_IN_CIS) {
    return callback(null, user, context);
  }

  // we'll need the node-fetch module, to add support for timeouts
  const fetch = require('node-fetch@2.6.0');

  // we also need to decode the private key from base64 into a PEM format that `jsonwebtoken` understands
  // generated with:
  // import base64
  // import boto3
  // base64.b64encode(boto3.client('ssm').get_parameter(Name='/iam/cis/development/keys/access_provider', WithDecryption=True)['Parameter']['Value'].encode('ascii')).decode('ascii')
  const privateKey = Buffer.from(configuration.changeapi_auth0_private_key, 'base64');

  const getBearerToken = async () => {
    // if we have the bearer token stored, we don't need to fetch it again
    if (global.personapi_bearer_token &&
        global.personapi_bearer_token_creation_time &&
        Date.now() - global.personapi_bearer_token_creation_time < PERSONAPI_BEARER_TOKEN_REFRESH_AGE) {
      return global.personapi_bearer_token;
    }

    console.log('Retrieving bearer token to create new user in CIS');

    const options = {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      timeout: PERSONAPI_TIMEOUT,
      body: JSON.stringify({
        audience: configuration.personapi_audience,
        client_id: configuration.personapi_client_id,
        client_secret: configuration.personapi_client_secret,
        grant_type: 'client_credentials',
      })
    };

    try {
      const response = await fetch(configuration.personapi_oauth_url, options);
      const data = await response.json();

      // store the bearer token in the global object, so it's not constantly retrieved
      global.personapi_bearer_token = data.access_token;
      global.personapi_bearer_token_creation_time = Date.now();

      console.log(`Successfully retrieved bearer token from Auth0`);
      return global.personapi_bearer_token;
    } catch (error) {
      throw Error(`Unable to retrieve bearer token from Auth0: ${error.message}`);
    }
  };

  const createPersonProfile = async () => {
    console.log(`Generating CIS profile for ${user.user_id}`);

    let now = new Date();
    now = now.toISOString();

    // load the user skeleton, as generated by:
    // base64.b64encode(json.dumps(requests.get('https://raw.githubusercontent.com/mozilla-iam/cis/master/python-modules/cis_profile/cis_profile/data/user_profile_null.json').json(), separators=(',', ':')).encode('ascii'))
    const profile = JSON.parse(Buffer.from(configuration.changeapi_null_profile, 'base64'));

    // update attributes in the skeleton
    // normally we shouldn't need to change anything but the values, but this is manually doing it because
    // I have no idea if the skeleton will ever change underneath me
    profile.active.metadata.last_modified = now;
    profile.active.signature.publisher.name = PUBLISHER_NAME;
    profile.active.value = true;

    profile.first_name.metadata.display = 'private';
    profile.first_name.metadata.last_modified = now;
    profile.first_name.signature.publisher.name = PUBLISHER_NAME;
    profile.first_name.value = user.given_name ? user.given_name : ' ';

    profile.last_name.metadata.display = 'private';
    profile.last_name.metadata.last_modified = now;
    profile.last_name.signature.publisher.name = PUBLISHER_NAME;
    profile.last_name.value = user.family_name ? user.family_name : ' ';

    profile.primary_email.metadata.last_modified = now;
    profile.primary_email.signature.publisher.name = PUBLISHER_NAME;
    profile.primary_email.value = user.email;

    profile.user_id.metadata.last_modified = now;
    profile.user_id.signature.publisher.name = PUBLISHER_NAME;
    profile.user_id.value = user.user_id;

    // now we need to go and update the identities values; this is based on the logic here:
    // https://github.com/mozilla-iam/cis/blob/master/python-modules/cis_publisher/cis_publisher/auth0.py
    // which may or may not be correct, I dunno
    for (let i = 0; i < user.identities.length; i++) {
      const identity = user.identities[i];
      // ignore a provider if it's not whitelisted
      if (!WHITELISTED_CONNECTIONS.includes(identity.connection)) {
        continue;
      }

      // store the login_method for the first identity
      console.log(`oh hey i is ${i}`);
      if (i === 0) {
        console.log(`setting login method for ${i}`);
        profile.login_method.metadata.last_modified = now;
        profile.login_method.signature.publisher.name = PUBLISHER_NAME;
        profile.login_method.value = identity.connection;
      }

      if (identity.provider === 'github') {
        profile.identities.github_id_v3.metadata.display = 'private';
        profile.identities.github_id_v3.metadata.last_modified = now;
        profile.identities.github_id_v3.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.github_id_v3.value = identity.user_id;

        if (user.nickname) {
          profile.usernames.metadata.display = 'private';
          profile.usernames.signature.publisher.name = PUBLISHER_NAME;
          profile.usernames.value = {"HACK#GITHUB": user.nickname};
        }

        if (identity.profileData) {
          // I could never seem to find a user that met this condition
          profile.identities.github_id_v4.metadata.display = 'private';
          profile.identities.github_id_v4.metadata.last_modified = now;
          profile.identities.github_id_v4.signature.publisher.name = PUBLISHER_NAME;
          profile.identities.github_id_v4.value = identity.profileData.node_id;

          profile.identities.github_primary_email.metadata.display = 'private';
          profile.identities.github_primary_email.metadata.last_modified = now;
          profile.identities.github_primary_email.metadata.verified = identity.profileData.email_verified === true ? true : false;
          profile.identities.github_primary_email.signature.publisher.name = PUBLISHER_NAME;
          profile.identities.github_primary_email.value = identity.profileData.email;
        }
      }

      else if (identity.provider === 'google-oauth2') {
        profile.identities.google_oauth2_id.metadata.display = 'private';
        profile.identities.google_oauth2_id.metadata.last_modified = now;
        profile.identities.google_oauth2_id.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.google_oauth2_id.value = identity.user_id;

        profile.identities.google_primary_email.metadata.display = 'private';
        profile.identities.google_primary_email.metadata.last_modified = now;
        profile.identities.google_primary_email.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.google_primary_email.value = user.email;
      }

      else if (identity.connection === 'firefoxaccounts' && identity.provider === 'oauth2') {
        profile.identities.firefox_accounts_id.metadata.display = 'private';
        profile.identities.firefox_accounts_id.metadata.last_modified = now;
        profile.identities.firefox_accounts_id.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.firefox_accounts_id.value = identity.user_id;

        profile.identities.firefox_accounts_primary_email.metadata.display = 'private';
        profile.identities.firefox_accounts_primary_email.metadata.last_modified = now;
        profile.identities.firefox_accounts_primary_email.signature.publisher.name = PUBLISHER_NAME;
        profile.identities.firefox_accounts_primary_email.value = user.email;
      }
    }

    // now, we need to sign every field and subfield
    signAll(profile);

    // turn this on only for debugging
    // console.log(`Generated profile:\n${JSON.stringify(profile, null, 2)}`);
    return profile;
  };

  const getPersonProfile = async () => {
    const bearer = await getBearerToken();
    const options = {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${bearer}`,
      },
      timeout: PERSONAPI_TIMEOUT,
    };
    const url = `${configuration.personapi_url}/v2/user/user_id/${encodeURI(user.user_id)}?active=any`;

    console.log(`Fetching person profile of ${user.user_id}`);

    const response = await fetch(url, options);

    return response.json();
  };

  const postProfile = async profile => {
    console.log(`Posting profile for ${user.user_id} to ChangeAPI`);

    const bearer = await getBearerToken();
    const options = {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${bearer}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(profile),
      timeout: CHANGEAPI_TIMEOUT,
    };
    const url = `${configuration.changeapi_url}/v2/user?user_id=${encodeURI(user.user_id)}`;

    // POST the profile to the ChangeAPI
    const response = await fetch(url, options);

    return response.json();
  };

  const signAll = profile => {
    // now we need to sign every attribute in the profile, if we're allowed to
    // otherwise, we will descend one level deep for sub attributes
    // this is super hacky and ugly and I hate it
    Object.values(profile).forEach(attr => {  // works because profile is mutable
      if (attr.constructor === Object && attr.signature) {
        signAttribute(attr);
      } else if (attr.constructor === Object && !attr.signature) {
        signAll(attr);  // descend deeper
      }
    });
  };

  const signAttribute = attr => {
    const jwt = require('jsonwebtoken');

    // we can only sign attributes that access_provider (e.g. auth0) is allowed to sign
    // we also ignore things that don't have a pre-existing signature field
    // we also don't need to sign null attributes
    if (!attr.signature || attr.signature.publisher.name !== PUBLISHER_NAME || attr.value === null || attr.values === null) {
      return attr;
    }

    // we need to delete the existing signature and generate it anew
    delete(attr.signature);

    attr.signature = {
      additional: [{
        alg: 'RS256',
        name: null,
        typ: 'JWS',
        value: '',
      }],
      publisher: {
        alg: 'RS256',
        name: PUBLISHER_NAME,
        typ: 'JWS',
        value: jwt.sign(attr, privateKey, { algorithm: 'RS256', noTimestamp: true }),
      }
    };

    return attr;
  };

  // if we get this far, we need to 1) call the PersonAPI to check for existance, and 2) if the user
  // doesn't exist, call the ChangeAPI to create them
  getPersonProfile()
    .then(profile => {
      if (Object.keys(profile).length !== 0) {
        throw Error(`Profile for ${user.user_id} already exists in PersonAPI`);
      } else {
        return createPersonProfile();
      }
    })
    .then(profile => postProfile(profile))
    .then(response => {
      if (response.constructor === Object && response.status_code === 200) {
        console.log(`Successfully created profile for ${user.user_id} in ChangeAPI`);
        return callback(null, user, context);
      } else {
        throw Error(`Unable to create profile for ${user.user_id} in ChangeAPI`);
      }
    })
    .catch(error => {
      console.log(`Error: ${error.message}`);
      return callback(null, user, context);
    });
}
<|endoftext|>