<commit_before>// Global variable definition
var canvas;
var gl;
var shaderProgram;

// Buffers
var triangleVertexPositionBuffer;
var squareVertexPositionBuffer;/Users/cirilbohak/git/webgl-lessons/scripts/02-colors.js

// Model-View and Projection matrices
var mvMatrix = mat4.create();
var pMatrix = mat4.create();

//
// initGL
//
// Initialize WebGL, returning the GL context or null if
// WebGL isn't available or could not be initialized.
//
function initGL(canvas) {
  var gl = null;
  try {
    // Try to grab the standard context. If it fails, fallback to experimental.
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
  } catch(e) {}

  // If we don't have a GL context, give up now
  if (!gl) {
    alert("Unable to initialize WebGL. Your browser may not support it.");
  }
  return gl;
}

//
// getShader
//
// Loads a shader program by scouring the current document,
// looking for a script with the specified ID.
//
function getShader(gl, id) {
  var shaderScript = document.getElementById(id);

  // Didn't find an element with the specified ID; abort.
  if (!shaderScript) {
    return null;
  }

  // Walk through the source element's children, building the
  // shader source string.
  var shaderSource = "";
  var currentChild = shaderScript.firstChild;
  while (currentChild) {
    if (currentChild.nodeType == 3) {
        shaderSource += currentChild.textContent;
    }
    currentChild = currentChild.nextSibling;
  }

  // Now figure out what type of shader script we have,
  // based on its MIME type.
  var shader;
  if (shaderScript.type == "x-shader/x-fragment") {
    shader = gl.createShader(gl.FRAGMENT_SHADER);
  } else if (shaderScript.type == "x-shader/x-vertex") {
    shader = gl.createShader(gl.VERTEX_SHADER);
  } else {
    return null;  // Unknown shader type
  }

  // Send the source to the shader object
  gl.shaderSource(shader, shaderSource);

  // Compile the shader program
  gl.compileShader(shader);

  // See if it compiled successfully
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert(gl.getShaderInfoLog(shader));
    return null;
  }

  return shader;
}

//
// initShaders
//
// Initialize the shaders, so WebGL knows how to light our scene.
//
function initShaders() {
  var fragmentShader = getShader(gl, "shader-fs");
  var vertexShader = getShader(gl, "shader-vs");

  // Create the shader program
  shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);

  // If creating the shader program failed, alert
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert("Unable to initialize the shader program.");
  }

  // start using shading program for rendering
  gl.useProgram(shaderProgram);

  // store location of aVertexPosition variable defined in shader
  shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");

  // turn on vertex position attribute at specified position
  gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

  // store location of uPMatrix variable defined in shader - projection matrix
  shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
  // store location of uMVMatrix variable defined in shader - model-view matrix
  shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}

//
// setMatrixUniforms
//
// Set the uniform values in shaders for model-view and projection matrix.
//
function setMatrixUniforms() {
  gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
  gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}

//
// initBuffers
//
// Initialize the buffers we'll need. For this demo, we just have
// one object -- a simple two-dimensional triangle and square.
//
function initBuffers() {
  // TRIANGLE
  // Create a buffer for the triangle's vertices.
  triangleVertexPositionBuffer = gl.createBuffer();

  // Select the triangleVertexPositionBuffer as the one to apply vertex
  // operations to from here out.
  gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
  var vertices = [
     0.0,  1.0,  0.0,
    -1.0, -1.0,  0.0,
     1.0, -1.0,  0.0
  ];

  // Pass the list of vertices into WebGL to build the shape. We
  // do this by creating a Float32Array from the JavaScript array,
  // then use it to fill the current vertex buffer.
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  triangleVertexPositionBuffer.itemSize = 3;
  triangleVertexPositionBuffer.numItems = 3;

  // SQUARE
  // Create a buffer for the square's vertices.
  squareVertexPositionBuffer = gl.createBuffer();

  // Select the squareVertexPositionBuffer as the one to apply vertex
  // operations to from here out.
  gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);

  // Now create an array of vertices for the square. Note that the Z
  // coordinate is always 0 here.
  vertices = [
     1.0,  1.0,  0.0,
    -1.0,  1.0,  0.0,
     1.0, -1.0,  0.0,
    -1.0, -1.0,  0.0
  ];

  // Now pass the list of vertices into WebGL to build the shape. We
  // do this by creating a Float32Array from the JavaScript array,
  // then use it to fill the current vertex buffer.
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  squareVertexPositionBuffer.itemSize = 3;
  squareVertexPositionBuffer.numItems = 4;
}

//
// drawScene
//
// Draw the scene.
//
function drawScene() {
  // set the rendering environment to full canvas size
  gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);

  // Clear the canvas before we start drawing on it.
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  // Establish the perspective with which we want to view the
  // scene. Our field of view is 45 degrees, with a width/height
  // ratio, and we only want to see objects between 0.1 units
  // and 100 units away from the camera.
  mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

  // Set the drawing position to the "identity" point, which is
  // the center of the scene.
  mat4.identity(mvMatrix);

  // TRIANGLE:

  // Now move the drawing position a bit to where we want to start
  // drawing the triangle.
  mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);

  // Draw the triangle by binding the array buffer to the square's vertices
  // array, setting attributes, and pushing it to GL.
  gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
  setMatrixUniforms();
  gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);

  // SQUARE:

  // Now move the drawing position a bit to where we want to start
  // drawing the square.
  mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);

  // Draw the square by binding the array buffer to the square's vertices
  // array, setting attributes, and pushing it to GL.
  gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
  setMatrixUniforms();
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}

//
// start
//
// Called when the canvas is created to get the ball rolling.
// Figuratively, that is. There's nothing moving in this demo.
//
function start() {
  canvas = document.getElementById("glcanvas");

  gl = initGL(canvas);      // Initialize the GL context

  // Only continue if WebGL is available and working
  if (gl) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);                      // Set clear color to black, fully opaque
    gl.clearDepth(1.0);                                     // Clear everything
    gl.enable(gl.DEPTH_TEST);                               // Enable depth testing
    gl.depthFunc(gl.LEQUAL);                                // Near things obscure far things

    // Initialize the shaders; this is where all the lighting for the
    // vertices and so forth is established.
    initShaders();

    // Here's where we call the routine that builds all the objects
    // we'll be drawing.
    initBuffers();

    // Set up to draw the scene periodically every 15ms.
    setInterval(drawScene, 15);
  }
}
<commit_msg>Removed typo.<commit_after>// Global variable definition
var canvas;
var gl;
var shaderProgram;

// Buffers
var triangleVertexPositionBuffer;
var squareVertexPositionBuffer;

// Model-View and Projection matrices
var mvMatrix = mat4.create();
var pMatrix = mat4.create();

//
// initGL
//
// Initialize WebGL, returning the GL context or null if
// WebGL isn't available or could not be initialized.
//
function initGL(canvas) {
  var gl = null;
  try {
    // Try to grab the standard context. If it fails, fallback to experimental.
    gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
  } catch(e) {}

  // If we don't have a GL context, give up now
  if (!gl) {
    alert("Unable to initialize WebGL. Your browser may not support it.");
  }
  return gl;
}

//
// getShader
//
// Loads a shader program by scouring the current document,
// looking for a script with the specified ID.
//
function getShader(gl, id) {
  var shaderScript = document.getElementById(id);

  // Didn't find an element with the specified ID; abort.
  if (!shaderScript) {
    return null;
  }

  // Walk through the source element's children, building the
  // shader source string.
  var shaderSource = "";
  var currentChild = shaderScript.firstChild;
  while (currentChild) {
    if (currentChild.nodeType == 3) {
        shaderSource += currentChild.textContent;
    }
    currentChild = currentChild.nextSibling;
  }

  // Now figure out what type of shader script we have,
  // based on its MIME type.
  var shader;
  if (shaderScript.type == "x-shader/x-fragment") {
    shader = gl.createShader(gl.FRAGMENT_SHADER);
  } else if (shaderScript.type == "x-shader/x-vertex") {
    shader = gl.createShader(gl.VERTEX_SHADER);
  } else {
    return null;  // Unknown shader type
  }

  // Send the source to the shader object
  gl.shaderSource(shader, shaderSource);

  // Compile the shader program
  gl.compileShader(shader);

  // See if it compiled successfully
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert(gl.getShaderInfoLog(shader));
    return null;
  }

  return shader;
}

//
// initShaders
//
// Initialize the shaders, so WebGL knows how to light our scene.
//
function initShaders() {
  var fragmentShader = getShader(gl, "shader-fs");
  var vertexShader = getShader(gl, "shader-vs");

  // Create the shader program
  shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);

  // If creating the shader program failed, alert
  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert("Unable to initialize the shader program.");
  }

  // start using shading program for rendering
  gl.useProgram(shaderProgram);

  // store location of aVertexPosition variable defined in shader
  shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");

  // turn on vertex position attribute at specified position
  gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

  // store location of uPMatrix variable defined in shader - projection matrix
  shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
  // store location of uMVMatrix variable defined in shader - model-view matrix
  shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
}

//
// setMatrixUniforms
//
// Set the uniform values in shaders for model-view and projection matrix.
//
function setMatrixUniforms() {
  gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);
  gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);
}

//
// initBuffers
//
// Initialize the buffers we'll need. For this demo, we just have
// one object -- a simple two-dimensional triangle and square.
//
function initBuffers() {
  // TRIANGLE
  // Create a buffer for the triangle's vertices.
  triangleVertexPositionBuffer = gl.createBuffer();

  // Select the triangleVertexPositionBuffer as the one to apply vertex
  // operations to from here out.
  gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
  var vertices = [
     0.0,  1.0,  0.0,
    -1.0, -1.0,  0.0,
     1.0, -1.0,  0.0
  ];

  // Pass the list of vertices into WebGL to build the shape. We
  // do this by creating a Float32Array from the JavaScript array,
  // then use it to fill the current vertex buffer.
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  triangleVertexPositionBuffer.itemSize = 3;
  triangleVertexPositionBuffer.numItems = 3;

  // SQUARE
  // Create a buffer for the square's vertices.
  squareVertexPositionBuffer = gl.createBuffer();

  // Select the squareVertexPositionBuffer as the one to apply vertex
  // operations to from here out.
  gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);

  // Now create an array of vertices for the square. Note that the Z
  // coordinate is always 0 here.
  vertices = [
     1.0,  1.0,  0.0,
    -1.0,  1.0,  0.0,
     1.0, -1.0,  0.0,
    -1.0, -1.0,  0.0
  ];

  // Now pass the list of vertices into WebGL to build the shape. We
  // do this by creating a Float32Array from the JavaScript array,
  // then use it to fill the current vertex buffer.
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  squareVertexPositionBuffer.itemSize = 3;
  squareVertexPositionBuffer.numItems = 4;
}

//
// drawScene
//
// Draw the scene.
//
function drawScene() {
  // set the rendering environment to full canvas size
  gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);

  // Clear the canvas before we start drawing on it.
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  // Establish the perspective with which we want to view the
  // scene. Our field of view is 45 degrees, with a width/height
  // ratio, and we only want to see objects between 0.1 units
  // and 100 units away from the camera.
  mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

  // Set the drawing position to the "identity" point, which is
  // the center of the scene.
  mat4.identity(mvMatrix);

  // TRIANGLE:

  // Now move the drawing position a bit to where we want to start
  // drawing the triangle.
  mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);

  // Draw the triangle by binding the array buffer to the square's vertices
  // array, setting attributes, and pushing it to GL.
  gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
  setMatrixUniforms();
  gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);

  // SQUARE:

  // Now move the drawing position a bit to where we want to start
  // drawing the square.
  mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);

  // Draw the square by binding the array buffer to the square's vertices
  // array, setting attributes, and pushing it to GL.
  gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
  gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
  setMatrixUniforms();
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
}

//
// start
//
// Called when the canvas is created to get the ball rolling.
// Figuratively, that is. There's nothing moving in this demo.
//
function start() {
  canvas = document.getElementById("glcanvas");

  gl = initGL(canvas);      // Initialize the GL context

  // Only continue if WebGL is available and working
  if (gl) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);                      // Set clear color to black, fully opaque
    gl.clearDepth(1.0);                                     // Clear everything
    gl.enable(gl.DEPTH_TEST);                               // Enable depth testing
    gl.depthFunc(gl.LEQUAL);                                // Near things obscure far things

    // Initialize the shaders; this is where all the lighting for the
    // vertices and so forth is established.
    initShaders();

    // Here's where we call the routine that builds all the objects
    // we'll be drawing.
    initBuffers();

    // Set up to draw the scene periodically every 15ms.
    setInterval(drawScene, 15);
  }
}
<|endoftext|>


<commit_before>'use strict';
var fs = require('fs')
var https = require('https');
var q = require('q');
var GnAuth = require('./gn-auth');
var sdkPackage = require('../package.json');
const  axios = require('axios');

function GnEndpoints(options, constants) {
	// options.baseUrl = options.sandbox ? constants.URL.DEFAULT.sandbox : constants.URL.DEFAULT.production;
	this.options = options;
	this.accessToken = null;
	this.constants = constants;
}

GnEndpoints.prototype.run = function (name, params, body) {
	var self = this;
	self.defer = q.defer();

	if (self.constants.ENDPOINTS.PIX.hasOwnProperty(name)) {
			try {
				this.certificate = fs.readFileSync(this.options.pathCert);
				this.agent = new https.Agent({
					pfx: this.certificate,
					passphrase: "",
				});
			} catch (error) {
				throw 'FALHA AO LER O CERTIFICADO'
			}

		self.endpoint = self.constants.ENDPOINTS.PIX[name];
		this.options.baseUrl = this.options.sandbox ? this.constants.URL.PIX.sandbox : this.constants.URL.PIX.production;
	}else{

		self.endpoint = self.constants.ENDPOINTS.DEFAULT[name];
		this.options.baseUrl = this.options.sandbox ? this.constants.URL.DEFAULT.sandbox : this.constants.URL.DEFAULT.production;
	}
	self.body = body;
	self.params = params;

	if (!self.accessToken) {
		self.getAccessToken()
			.then(self.directReq.bind(self));
	} else {
		self.withTokenReq.call(self);
	}

	return self.defer.promise;
};

GnEndpoints.prototype.getAccessToken = function () {
	var self = this;
	var gnAuth = new GnAuth(this.options, this.constants);
	return gnAuth.getAccessToken()
		.then(function (response) {
			self.accessToken = response.access_token;
			return self.accessToken;
		}).catch(function (err) {
			return err;
		});
};

GnEndpoints.prototype.getResponse = function (response, body) {
	return this.options
		.raw_response ? response : body;
};

GnEndpoints.prototype.req = async function (callback) {
	let req = this.getParams.call(this, this.endpoint.route);
	req['method'] = this.endpoint.method;
	const response = axios(req)
		.then((res) => {
			callback(res);
			console.log(res.data)
		})
		.catch((error) => {
			callback(error);
			console.log(error.response.data);
		});
};

GnEndpoints.prototype.directReq = function () {
	this.req(this.directReqCallback.bind(this));
};

GnEndpoints.prototype.withTokenReq = function () {
	this.req(this.withTokenReqCallback.bind(this));
};

GnEndpoints.prototype.getParams = function (route) {
	var self = this;
	var regex = /\:(\w+)/g;
	var query = '';
	var placeholders = route.match(regex) || [];
	var params = {};

	for (var prop in self.params) {
		params[prop] = self.params[prop];
	}

	var getVariables = function () {
		return placeholders.map(function (item) {
			return item.replace(':', '');
		});
	};

	var updateRoute = function () {
		var variables = getVariables();
		variables.forEach(function (value, index) {
			if (params[value]) {
				route = route.replace(placeholders[index], params[value]);
				delete params[value];
			}
		});
	};

	var getQueryString = function () {
		var keys = Object.keys(params);
		var initial = keys.length >= 1 ? '?' : '';
		return keys.reduce(function (previous, current, index, array) {
			var next = (index === array.length - 1) ? '' : '&';
			return [previous, current, '=',
				params[current], next
			].join('');
		}, initial);
	};

	updateRoute();
	query = getQueryString();

	var headers = {
		'api-sdk': 'node-' + sdkPackage.version,
		'Content-Type': 'application/json',
		'authorization': "Bearer " + this.accessToken
	};

	if (this.options.partner_token) {
		headers['partner-token'] = this.options.partnerToken;
	}
	if (this.options.validateMtls) {
		headers['x-skip-mtls-checking'] = false;
	} else {
		headers['x-skip-mtls-checking'] = true;
	}

	var req = {
		url: [this.options.baseUrl, route, query].join(''),
		headers: headers,
		data: this.body
	}

	if (this.constants.URL.PIX.production == this.options.baseUrl || this.constants.URL.PIX.sandbox == this.options.baseUrl) {
		req['httpsAgent'] = this.agent
	}

	return req;
};

GnEndpoints.prototype.withTokenReqCallback = function (err, httpResponse, httpResponseBody) {
	var self = this;
	var response = self.getResponse(httpResponse, httpResponseBody);

	if (err) {
		self.defer.reject(err);
	} else if (httpResponse.statusCode === 401) {
		self.getAccessToken().then(self.directReq.bind(self));
	} else if (httpResponse.statusCode !== 200) {
		self.defer.reject(response);
	} else {
		self.defer.resolve(response);
	}
};

GnEndpoints.prototype.directReqCallback = function (err, httpResponse, bodyResponse) {
	var response = this.getResponse(httpResponse, bodyResponse);

	if (err) {
		this.defer.reject(err);
	} else if (httpResponse.statusCode !== 200) {
		this.defer.reject(response);
	} else {
		this.defer.resolve(response);
	}
};

module.exports = GnEndpoints;<commit_msg>Update gn-endpoints: Correção do tratamento de responses<commit_after>'use strict';
var fs = require('fs')
var https = require('https');
var q = require('q');
var GnAuth = require('./gn-auth');
var sdkPackage = require('../package.json');
const axios = require('axios');

function GnEndpoints(options, constants) {
	this.options = options;
	this.accessToken = null;
	this.constants = constants;
}

GnEndpoints.prototype.run = function (name, params, body) {
	this.defer = q.defer();
	if (this.constants.ENDPOINTS.PIX.hasOwnProperty(name)) {
		try {
			this.certificate = fs.readFileSync(this.options.pathCert);
			this.agent = new https.Agent({
				pfx: this.certificate,
				passphrase: "",
			});
		} catch (error) {
			throw 'FALHA AO LER O CERTIFICADO'
		}

		this.endpoint = this.constants.ENDPOINTS.PIX[name];
		this.options.baseUrl = this.options.sandbox ? this.constants.URL.PIX.sandbox : this.constants.URL.PIX.production;
	} else {

		this.endpoint = this.constants.ENDPOINTS.DEFAULT[name];
		this.options.baseUrl = this.options.sandbox ? this.constants.URL.DEFAULT.sandbox : this.constants.URL.DEFAULT.production;
	}
	this.body = body;
	this.params = params;

	this.getAccessToken().then(this.directReq.bind(this));

	return this.defer.promise;
};

GnEndpoints.prototype.getAccessToken = function () {
	let self = this;
	var gnAuth = new GnAuth(this.options, this.constants);
	return gnAuth.getAccessToken()
		.then(function (response) {
			self.accessToken = response.access_token;
			return response.access_token;
		}).catch(function (err) {
			return err;
		});
};

GnEndpoints.prototype.req = async function (callback) {
	let req = this.getParams.call(this, this.endpoint.route);
	req['method'] = this.endpoint.method;
	const response = axios(req)
		.then((res) => {
			callback(res);
		})
		.catch((error) => {
			callback(error);
		});
};

GnEndpoints.prototype.directReq = function () {
	this.req(this.directReqCallback.bind(this));
};

GnEndpoints.prototype.getParams = function (route) {
	var regex = /\:(\w+)/g;
	var query = '';
	var placeholders = route.match(regex) || [];
	var params = {};

	for (var prop in this.params) {
		params[prop] = this.params[prop];
	}

	var getVariables = function () {
		return placeholders.map(function (item) {
			return item.replace(':', '');
		});
	};

	var updateRoute = function () {
		var variables = getVariables();
		variables.forEach(function (value, index) {
			if (params[value]) {
				route = route.replace(placeholders[index], params[value]);
				delete params[value];
			}
		});
	};

	var getQueryString = function () {
		var keys = Object.keys(params);
		var initial = keys.length >= 1 ? '?' : '';
		return keys.reduce(function (previous, current, index, array) {
			var next = (index === array.length - 1) ? '' : '&';
			return [previous, current, '=',
				params[current], next
			].join('');
		}, initial);
	};

	updateRoute();
	query = getQueryString();

	var headers = {
		'api-sdk': 'node-' + sdkPackage.version,
		'Content-Type': 'application/json',
		'authorization': "Bearer " + this.accessToken
	};

	if (this.options.partner_token) {
		headers['partner-token'] = this.options.partnerToken;
	}
	if (this.options.validateMtls) {
		headers['x-skip-mtls-checking'] = false;
	} else {
		headers['x-skip-mtls-checking'] = true;
	}

	var req = {
		url: [this.options.baseUrl, route, query].join(''),
		headers: headers,
		data: this.body
	}

	if (this.constants.URL.PIX.production == this.options.baseUrl || this.constants.URL.PIX.sandbox == this.options.baseUrl) {
		req['httpsAgent'] = this.agent
	}

	return req;
};

GnEndpoints.prototype.directReqCallback = function (raw_response) {
	if (raw_response.status) {
		if (raw_response.status > 299) {
			this.defer.reject(raw_response.response.data)
		} else {
			if (raw_response.data.data)
				this.defer.resolve(raw_response.data.data);
			else
				this.defer.resolve(raw_response.data);
		}
	} else {
		if (raw_response.response.status > 299) {
			this.defer.reject(raw_response.response.data)
		} else {

		}
	}
};

module.exports = GnEndpoints;
<|endoftext|>


<commit_before>// Modules dependencies.
var EventEmitter = require('events').EventEmitter,
    etc = require('etc'),
    etcYaml = require('etc-yaml'),
    findPkg = require('witwip'),
    fs = require('fs'),
    path = require('path'),
    inherits = require('util').inherits,
    eventflow = require('eventflow');

/**
 * Cantina application class.
 */
function Cantina() {
  EventEmitter.call(this);
}
inherits(Cantina, EventEmitter);
eventflow(Cantina);

/**
 * Expose the paths to Cantina's core plugins.
 */
Cantina.prototype.plugins = {};
fs.readdirSync(path.join(__dirname, 'plugins')).forEach(function (plugin) {
  Cantina.prototype.plugins[path.basename(plugin)] = path.join(__dirname, 'plugins', plugin);
});

/**
 * Setup the app.
 */
Cantina.prototype.setup = function (root, callback) {
  this.conf = etc().use(etcYaml).argv().env();

  if (typeof root === 'function') {
    callback = root;
    root = null;
  }

  function _setup (done, err, pkgPath, pkgData) {
    if (err) return done(err);

    // Set pkg and root paths.
    this.pkgPath = pkgPath;
    this.pkgData = pkgData;
    this.root = path.dirname(pkgPath);

    // Add ./etc of parent.
    this.conf.folder(path.join(this.root, 'etc'));

    // Add package.json of the app.
    this.conf.pkg(this.pkgPath);

    // Load the utils plugin.
    require(this.plugins.utils);

    done();
  }

  if (root) {
    findPkg(root, _setup.bind(this, callback));
  }
  else {
    findPkg(path.dirname(module.parent.filename), _setup.bind(this, callback));
  }
};

/**
 * Initialize the app.
 */
Cantina.prototype.init = function (callback) {
  var app = this;

  app.series('init', function (err) {
    if (err) {
      if (callback) {
        callback(err);
      }
      else {
        app.emit('error', err);
      }
    }
    else {
      app.series('ready', function(err) {
        if (callback) callback(err);
      });
    }
  });
};

// Export a Cantina instance.
module.exports = new Cantina();
<commit_msg>Adds a default error handler if none are bound<commit_after>// Modules dependencies.
var EventEmitter = require('events').EventEmitter,
    etc = require('etc'),
    etcYaml = require('etc-yaml'),
    findPkg = require('witwip'),
    fs = require('fs'),
    path = require('path'),
    inherits = require('util').inherits,
    eventflow = require('eventflow');

/**
 * Cantina application class.
 */
function Cantina() {
  EventEmitter.call(this);
}
inherits(Cantina, EventEmitter);
eventflow(Cantina);

/**
 * Expose the paths to Cantina's core plugins.
 */
Cantina.prototype.plugins = {};
fs.readdirSync(path.join(__dirname, 'plugins')).forEach(function (plugin) {
  Cantina.prototype.plugins[path.basename(plugin)] = path.join(__dirname, 'plugins', plugin);
});

/**
 * Setup the app.
 */
Cantina.prototype.setup = function (root, callback) {
  this.conf = etc().use(etcYaml).argv().env();

  if (typeof root === 'function') {
    callback = root;
    root = null;
  }

  function _setup (done, err, pkgPath, pkgData) {
    if (err) return done(err);

    // Set pkg and root paths.
    this.pkgPath = pkgPath;
    this.pkgData = pkgData;
    this.root = path.dirname(pkgPath);

    // Add ./etc of parent.
    this.conf.folder(path.join(this.root, 'etc'));

    // Add package.json of the app.
    this.conf.pkg(this.pkgPath);

    // Load the utils plugin.
    require(this.plugins.utils);

    done();
  }

  if (root) {
    findPkg(root, _setup.bind(this, callback));
  }
  else {
    findPkg(path.dirname(module.parent.filename), _setup.bind(this, callback));
  }
};

/**
 * Initialize the app.
 */
Cantina.prototype.init = function (callback) {
  var app = this;

  // Default error handler.
  if (!app.listeners('error').length) {
    app.on('error', function(err) {
      console.error(err);
    });
  }

  // Run plugin 'init' and 'ready' listeners.
  app.series('init', function (err) {
    if (err) {
      if (callback)
        callback(err);
      else
        app.emit('error', err);
    }
    else {
      app.series('ready', function(err) {
        if (err) {
          if (callback)
            callback(err);
          else
            app.emit('error', err);
        }
        else if (callback) {
          callback();
        }
      });
    }
  });
};

// Export a Cantina instance.
module.exports = new Cantina();
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Steam Wishlist Discount Filter
// @description Adds the option to show only discounted items in your Steam wishlist.
// @namespace   Xiyng
// @include     https://steamcommunity.com/id/*/wishlist
// @include     https://steamcommunity.com/id/*/wishlist/*
// @version     1.1.2
// noframes
// @require     https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
// @grant       none
// @run-at      document-start
// ==/UserScript==

var showOnlyDiscountedItemsCheckbox;
var priceInput;
var percentageInput;
var unpricedItems;
var normallyPricedItems;
var discountedItems;
var inputTimer; // for adding a delay to updating the item list when changing minimum discount percentage
var maximumPrice = undefined;
var showOnlyDiscountedItems = false;
var minimumDiscountPercentage = undefined;

$(document).ready(initialize);

/**
 * Adds the toggle area, and gets the list of normally priced games.
 */
function initialize() {
    addControls();
    updateItemLists();
}

/**
 * Wishlist item details
 * @constructs itemDetails
 * @param {HTMLElement} node - Item node
 */
function itemDetails(node) {
    this.node = node;

    this.display = function(display) {
        var displayValue = display ? "block" : "none";
        this.node.style.display = displayValue;
    };
}

/**
 * Priced wishlist item details
 * @constructs itemDetails
 * @param {HTMLElement} node - Item node
 * @param {Number} price - The price of the item
 */
function pricedItemDetails(node, price) {
    itemDetails.call(this, node);
    this.price = price;
}

/**
 * Discounted wishlist item details
 * @constructs discountedItemDetails
 * @param {HTMLElement} node - Item node
 * @param {Number} price - The price of the item
 * @param {Number} discountPercentage - Discount percentage of the item
 */
function discountedItemDetails(node, price, discountPercentage) {
    pricedItemDetails.call(this, node, price);
    this.normalPrice = price;
    this.discountPercentage = discountPercentage;
}

/**
 * Adds controls for the script.
 */
function addControls() {
    var wishlist = document.getElementById("wishlist_items");
    var controls = document.createElement("div");
    controls.style.display = "inline";

    var priceDiv = document.createElement("div");
    priceDiv.style.textAlign = "right";

    var priceLabel = document.createElement("label");
    priceLabel.setAttribute("for", "maximumPriceInput");
    priceLabel.textContent = "Maximum price";
    priceLabel.style.marginRight = "0.5em";
    priceDiv.appendChild(priceLabel);

    priceInput = document.createElement("input");
    priceInput.id = "maximumPriceInput";
    priceInput.setAttribute("type", "text");
    priceInput.addEventListener("input", maximumPriceChanged);
    priceInput.style.width = "3.5em";
    priceDiv.appendChild(priceInput);

    controls.appendChild(priceDiv);

    var discountDiv = document.createElement("div");
    discountDiv.style.textAlign = "right";

    showOnlyDiscountedItemsCheckbox = document.createElement("input");
    showOnlyDiscountedItemsCheckbox.id = "showOnlyDiscountedItemsCheckbox";
    showOnlyDiscountedItemsCheckbox.setAttribute("type", "checkbox");
    showOnlyDiscountedItemsCheckbox.addEventListener("change", checkboxChanged);
    discountDiv.appendChild(showOnlyDiscountedItemsCheckbox);

    var checkboxLabel = document.createElement("label");
    checkboxLabel.setAttribute("for", "showOnlyDiscountedItemsCheckbox");
    checkboxLabel.textContent = "Show only discounted items";
    discountDiv.appendChild(checkboxLabel);

    controls.appendChild(discountDiv);

    percentageDiv = document.createElement("div");
    percentageDiv.style.textAlign = "right";

    var percentageLabel = document.createElement("label");
    percentageLabel.setAttribute("for", "discountPercentageInput");
    percentageLabel.textContent = "Minimum discount percentage";
    percentageLabel.style.marginRight = "0.5em";
    percentageDiv.appendChild(percentageLabel);

    percentageInput = document.createElement("input");
    percentageInput.id = "discountPercentageInput";
    percentageInput.setAttribute("type", "text");
    if (minimumDiscountPercentage) {
        percentageInput.setAttribute("value", minimumDiscountPercentage);
    }
    percentageInput.addEventListener("input", percentageDiscountChanged);
    percentageInput.style.width = "3.5em";
    percentageDiv.appendChild(percentageInput);

    updatePercentageDivVisibility();
    controls.appendChild(percentageDiv);

    enableInputElements(false);
    var disabledSaveAction = document.getElementById("save_action_disabled_1");
    disabledSaveAction.parentNode.insertBefore(controls, disabledSaveAction);
}

/**
 * Updates the visibility of the minimum percentage discount div element. The
 * element is visible when the filter is set to show only discounted items, and
 * otherwise it is be hidden.
 */
function updatePercentageDivVisibility() {
    var percentageDivVisibility = showOnlyDiscountedItems ? "visible" : "hidden";
    percentageDiv.style.visibility = percentageDivVisibility;
}

/**
 * Enables or disables the input elements.
 * @param {Boolean} enable - true enables the elements, false disables them
 */
function enableInputElements(enable) {
    showOnlyDiscountedItemsCheckbox.disabled = !enable;
    priceInput.disabled = !enable;
    percentageInput.disabled = !enable;
}

/**
 * Updates normallyPricedItems and discountedItems. Also disables the input
 * elements at start, then re-enables them at end.
 */
function updateItemLists() {
    enableInputElements(false);

    unpricedItems = [];
    normallyPricedItems = [];
    discountedItems = [];

    var wishlist = document.getElementById("wishlist_items");
    var wishlistItems = wishlist.children;
    for (var i = 0; i < wishlistItems.length; i++) {
        var wishlistItem = wishlistItems[i];
        var priceData = wishlistItem
            .getElementsByClassName("wishlistRowItem")[0]
            .getElementsByClassName("gameListPriceData")[0];
        var discount = priceData
            .getElementsByClassName("discount_block discount_block_inline");
        if (discount.length < 1) {
            var priceElements = priceData.getElementsByClassName("price");
            if (!priceElements || priceElements.length < 1) {
                var item = new itemDetails(wishlistItem);
                unpricedItems.push(item);
            }
            else {
                var priceText = priceElements[0].textContent.trim();
                if (priceText !== '' && isNaN(priceText.charAt(0))) {
                    priceText = priceText.substr(1);
                }
                var price = parseFloat(priceText.replace(",", ".")); // should work in most cases - not all
                if (price !== '' && isNaN(price)) {
                    price = 0; // *probably* a free-to-play title
                }
                var item = new pricedItemDetails(wishlistItem, price);
                normallyPricedItems.push(item);
            }
        }
        else {
            discount = discount[0];
            var discountPercentageText = discount
                .getElementsByClassName("discount_pct")[0]
                .textContent;
            var discountPercentage = -parseInt(discountPercentageText);
            var priceText = discount
                .getElementsByClassName("discount_prices")[0]
                .getElementsByClassName("discount_final_price")[0]
                .textContent;
            var price = parseFloat(priceText);
            var item = new discountedItemDetails(
                wishlistItem, price, discountPercentage
            );
            discountedItems.push(item);
        }
    }

    enableInputElements(true);
}

/**
 * Handles changes in the status of the checkbox. In practice, mainly updates
 * the list of visible items.
 */
function checkboxChanged() {
    showOnlyDiscountedItems = showOnlyDiscountedItemsCheckbox.checked;
    if (showOnlyDiscountedItems && inputTimer) {
        clearInputTimer();
    }
    updatePercentageDivVisibility();
    updateShownItems();
}

function maximumPriceChanged() {
    var input = priceInput.value;
    if (input === "") {
        maximumPrice = undefined;
    }

    var inputValue = Number.parseInt(input);
    maximumPrice = isNaN(inputValue) ? undefined : inputValue;

    if (inputTimer) {
        clearInputTimer();
    }
    var callback = function() {
        updateShownItems();
    };
    inputTimer = setTimeout(callback, 500);
}

/**
 * Handles updating the item list when the minimum discount percentage input
 * field is changed.
 */
function percentageDiscountChanged() {
    var input = percentageInput.value;
    if (input === "") {
        minimumDiscountPercentage = undefined;
    }

    var inputValue = Number.parseInt(input);
    minimumDiscountPercentage = isNaN(inputValue) ? undefined : inputValue;

    if (inputTimer) {
        clearInputTimer();
    }
    var callback = function() {
        updateShownItems();
    };
    inputTimer = setTimeout(callback, 500);
}

/**
 * Clears the input timer.
 */
function clearInputTimer() {
    if (inputTimer) {
        clearTimeout(inputTimer);
        inputTimer = null;
    }
}

/**
 * Updates the list of shown items according to the maximum price and minimum
 * discount percentage.
 */
function updateShownItems() {
    var maximumPriceSet = !isNaN(maximumPrice);
    var minimumDiscountPercentageSet = !isNaN(minimumDiscountPercentage);
    var showUndiscountedItems =
        !showOnlyDiscountedItems ||
        (minimumDiscountPercentageSet && minimumDiscountPercentage === 0);

    for (var i = 0; i < unpricedItems.length; i++) {
        var item = unpricedItems[i];
        item.display(showUndiscountedItems);
    }

    for (var i = 0; i < normallyPricedItems.length; i++) {
        var item = normallyPricedItems[i];
        var priceGoodEnough =
            maximumPriceSet ? item.price <= maximumPrice : true;
        item.display(showUndiscountedItems && priceGoodEnough);
    }

    for (var i = 0; i < discountedItems.length; i++) {
        var item = discountedItems[i];
        var discountGoodEnough = minimumDiscountPercentageSet ?
            item.discountPercentage >= minimumDiscountPercentage : true;
        var priceGoodEnough = maximumPriceSet ?
            item.price <= maximumPrice : true;
        var showItem = discountGoodEnough && priceGoodEnough;
        item.display(showItem);
    }
}
<commit_msg>Add an explicit declaration for a global variable<commit_after>// ==UserScript==
// @name        Steam Wishlist Discount Filter
// @description Adds the option to show only discounted items in your Steam wishlist.
// @namespace   Xiyng
// @include     https://steamcommunity.com/id/*/wishlist
// @include     https://steamcommunity.com/id/*/wishlist/*
// @version     1.1.2
// noframes
// @require     https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
// @grant       none
// @run-at      document-start
// ==/UserScript==

var showOnlyDiscountedItemsCheckbox;
var priceInput;
var percentageDiv;
var percentageInput;
var unpricedItems;
var normallyPricedItems;
var discountedItems;
var inputTimer; // for adding a delay to updating the item list when changing minimum discount percentage
var maximumPrice = undefined;
var showOnlyDiscountedItems = false;
var minimumDiscountPercentage = undefined;

$(document).ready(initialize);

/**
 * Adds the toggle area, and gets the list of normally priced games.
 */
function initialize() {
    addControls();
    updateItemLists();
}

/**
 * Wishlist item details
 * @constructs itemDetails
 * @param {HTMLElement} node - Item node
 */
function itemDetails(node) {
    this.node = node;

    this.display = function(display) {
        var displayValue = display ? "block" : "none";
        this.node.style.display = displayValue;
    };
}

/**
 * Priced wishlist item details
 * @constructs itemDetails
 * @param {HTMLElement} node - Item node
 * @param {Number} price - The price of the item
 */
function pricedItemDetails(node, price) {
    itemDetails.call(this, node);
    this.price = price;
}

/**
 * Discounted wishlist item details
 * @constructs discountedItemDetails
 * @param {HTMLElement} node - Item node
 * @param {Number} price - The price of the item
 * @param {Number} discountPercentage - Discount percentage of the item
 */
function discountedItemDetails(node, price, discountPercentage) {
    pricedItemDetails.call(this, node, price);
    this.normalPrice = price;
    this.discountPercentage = discountPercentage;
}

/**
 * Adds controls for the script.
 */
function addControls() {
    var wishlist = document.getElementById("wishlist_items");
    var controls = document.createElement("div");
    controls.style.display = "inline";

    var priceDiv = document.createElement("div");
    priceDiv.style.textAlign = "right";

    var priceLabel = document.createElement("label");
    priceLabel.setAttribute("for", "maximumPriceInput");
    priceLabel.textContent = "Maximum price";
    priceLabel.style.marginRight = "0.5em";
    priceDiv.appendChild(priceLabel);

    priceInput = document.createElement("input");
    priceInput.id = "maximumPriceInput";
    priceInput.setAttribute("type", "text");
    priceInput.addEventListener("input", maximumPriceChanged);
    priceInput.style.width = "3.5em";
    priceDiv.appendChild(priceInput);

    controls.appendChild(priceDiv);

    var discountDiv = document.createElement("div");
    discountDiv.style.textAlign = "right";

    showOnlyDiscountedItemsCheckbox = document.createElement("input");
    showOnlyDiscountedItemsCheckbox.id = "showOnlyDiscountedItemsCheckbox";
    showOnlyDiscountedItemsCheckbox.setAttribute("type", "checkbox");
    showOnlyDiscountedItemsCheckbox.addEventListener("change", checkboxChanged);
    discountDiv.appendChild(showOnlyDiscountedItemsCheckbox);

    var checkboxLabel = document.createElement("label");
    checkboxLabel.setAttribute("for", "showOnlyDiscountedItemsCheckbox");
    checkboxLabel.textContent = "Show only discounted items";
    discountDiv.appendChild(checkboxLabel);

    controls.appendChild(discountDiv);

    percentageDiv = document.createElement("div");
    percentageDiv.style.textAlign = "right";

    var percentageLabel = document.createElement("label");
    percentageLabel.setAttribute("for", "discountPercentageInput");
    percentageLabel.textContent = "Minimum discount percentage";
    percentageLabel.style.marginRight = "0.5em";
    percentageDiv.appendChild(percentageLabel);

    percentageInput = document.createElement("input");
    percentageInput.id = "discountPercentageInput";
    percentageInput.setAttribute("type", "text");
    if (minimumDiscountPercentage) {
        percentageInput.setAttribute("value", minimumDiscountPercentage);
    }
    percentageInput.addEventListener("input", percentageDiscountChanged);
    percentageInput.style.width = "3.5em";
    percentageDiv.appendChild(percentageInput);

    updatePercentageDivVisibility();
    controls.appendChild(percentageDiv);

    enableInputElements(false);
    var disabledSaveAction = document.getElementById("save_action_disabled_1");
    disabledSaveAction.parentNode.insertBefore(controls, disabledSaveAction);
}

/**
 * Updates the visibility of the minimum percentage discount div element. The
 * element is visible when the filter is set to show only discounted items, and
 * otherwise it is be hidden.
 */
function updatePercentageDivVisibility() {
    var percentageDivVisibility = showOnlyDiscountedItems ? "visible" : "hidden";
    percentageDiv.style.visibility = percentageDivVisibility;
}

/**
 * Enables or disables the input elements.
 * @param {Boolean} enable - true enables the elements, false disables them
 */
function enableInputElements(enable) {
    showOnlyDiscountedItemsCheckbox.disabled = !enable;
    priceInput.disabled = !enable;
    percentageInput.disabled = !enable;
}

/**
 * Updates normallyPricedItems and discountedItems. Also disables the input
 * elements at start, then re-enables them at end.
 */
function updateItemLists() {
    enableInputElements(false);

    unpricedItems = [];
    normallyPricedItems = [];
    discountedItems = [];

    var wishlist = document.getElementById("wishlist_items");
    var wishlistItems = wishlist.children;
    for (var i = 0; i < wishlistItems.length; i++) {
        var wishlistItem = wishlistItems[i];
        var priceData = wishlistItem
            .getElementsByClassName("wishlistRowItem")[0]
            .getElementsByClassName("gameListPriceData")[0];
        var discount = priceData
            .getElementsByClassName("discount_block discount_block_inline");
        if (discount.length < 1) {
            var priceElements = priceData.getElementsByClassName("price");
            if (!priceElements || priceElements.length < 1) {
                var item = new itemDetails(wishlistItem);
                unpricedItems.push(item);
            }
            else {
                var priceText = priceElements[0].textContent.trim();
                if (priceText !== '' && isNaN(priceText.charAt(0))) {
                    priceText = priceText.substr(1);
                }
                var price = parseFloat(priceText.replace(",", ".")); // should work in most cases - not all
                if (price !== '' && isNaN(price)) {
                    price = 0; // *probably* a free-to-play title
                }
                var item = new pricedItemDetails(wishlistItem, price);
                normallyPricedItems.push(item);
            }
        }
        else {
            discount = discount[0];
            var discountPercentageText = discount
                .getElementsByClassName("discount_pct")[0]
                .textContent;
            var discountPercentage = -parseInt(discountPercentageText);
            var priceText = discount
                .getElementsByClassName("discount_prices")[0]
                .getElementsByClassName("discount_final_price")[0]
                .textContent;
            var price = parseFloat(priceText);
            var item = new discountedItemDetails(
                wishlistItem, price, discountPercentage
            );
            discountedItems.push(item);
        }
    }

    enableInputElements(true);
}

/**
 * Handles changes in the status of the checkbox. In practice, mainly updates
 * the list of visible items.
 */
function checkboxChanged() {
    showOnlyDiscountedItems = showOnlyDiscountedItemsCheckbox.checked;
    if (showOnlyDiscountedItems && inputTimer) {
        clearInputTimer();
    }
    updatePercentageDivVisibility();
    updateShownItems();
}

function maximumPriceChanged() {
    var input = priceInput.value;
    if (input === "") {
        maximumPrice = undefined;
    }

    var inputValue = Number.parseInt(input);
    maximumPrice = isNaN(inputValue) ? undefined : inputValue;

    if (inputTimer) {
        clearInputTimer();
    }
    var callback = function() {
        updateShownItems();
    };
    inputTimer = setTimeout(callback, 500);
}

/**
 * Handles updating the item list when the minimum discount percentage input
 * field is changed.
 */
function percentageDiscountChanged() {
    var input = percentageInput.value;
    if (input === "") {
        minimumDiscountPercentage = undefined;
    }

    var inputValue = Number.parseInt(input);
    minimumDiscountPercentage = isNaN(inputValue) ? undefined : inputValue;

    if (inputTimer) {
        clearInputTimer();
    }
    var callback = function() {
        updateShownItems();
    };
    inputTimer = setTimeout(callback, 500);
}

/**
 * Clears the input timer.
 */
function clearInputTimer() {
    if (inputTimer) {
        clearTimeout(inputTimer);
        inputTimer = null;
    }
}

/**
 * Updates the list of shown items according to the maximum price and minimum
 * discount percentage.
 */
function updateShownItems() {
    var maximumPriceSet = !isNaN(maximumPrice);
    var minimumDiscountPercentageSet = !isNaN(minimumDiscountPercentage);
    var showUndiscountedItems =
        !showOnlyDiscountedItems ||
        (minimumDiscountPercentageSet && minimumDiscountPercentage === 0);

    for (var i = 0; i < unpricedItems.length; i++) {
        var item = unpricedItems[i];
        item.display(showUndiscountedItems);
    }

    for (var i = 0; i < normallyPricedItems.length; i++) {
        var item = normallyPricedItems[i];
        var priceGoodEnough =
            maximumPriceSet ? item.price <= maximumPrice : true;
        item.display(showUndiscountedItems && priceGoodEnough);
    }

    for (var i = 0; i < discountedItems.length; i++) {
        var item = discountedItems[i];
        var discountGoodEnough = minimumDiscountPercentageSet ?
            item.discountPercentage >= minimumDiscountPercentage : true;
        var priceGoodEnough = maximumPriceSet ?
            item.price <= maximumPrice : true;
        var showItem = discountGoodEnough && priceGoodEnough;
        item.display(showItem);
    }
}
<|endoftext|>


<commit_before>var path = require('path');
var async = require('async');
var crypto = require('crypto');
var zlib = require('zlib');
var EventEmitter = require('events').EventEmitter;
var Router = require('../router');
var Storage = require('../storage');
var Processor = require('../processor');
var Security = require('../security');
var Throttle = require('./throttle');
var helpers = require('../helpers');

module.exports = Connect;

function Connect(options) {
  if (!(this instanceof Connect)) {
    return new Connect(options);
  }

  EventEmitter.call(this);

  this.options = options || {};
  this.options.stepTimeout = this.options.stepTimeout || 60000;

  var $this = this;
  var errHandler = function(err) {
    $this.emit('error', err);
  };
  var warnHandler = function(err) {
    $this.emit('warn', err);
  };

  this.router = this.options.router instanceof Router
    ? this.options.router
    : new Router(this.options.router)
  ;
  this.router.on('error', errHandler);
  this.router.on('warn', warnHandler);
  this.storage = this.options.storage instanceof Storage
    ? this.options.storage
    : new Storage(this.options.storage)
  ;
  this.storage.on('error', errHandler);
  this.storage.on('warn', warnHandler);
  this.processor = new Processor(this.options.processor);
  this.processor.on('error', errHandler);
  this.processor.on('warn', warnHandler);
  this.throttle = new Throttle(this.options.throttle);
  this.throttle.on('error', errHandler);
  this.throttle.on('warn', warnHandler);
  this.security = new Security(this.options.security);
  this.security.on('error', errHandler);
  this.security.on('warn', warnHandler);
}

var p = Connect.prototype = new EventEmitter();

p.getHandler = function() {
  var $this = this;

  return function(req, res) {
    if (!$this.throttle.startRequest(req, res)) {
      res.writeHead(503);
      return void res.end();
    }

    if (req.url === '/favicon.ico') {
      // reserved, never valid
      $this.emit('warn', '/favicon.ico not currently supported');
      res.writeHead(404);
      return void res.end();
    }

    var reqInfo;
    try {
      reqInfo = $this.router.getInfo(req);
    } catch (ex) {
      ex.url = req.url;
      $this.emit('error', ex);
      res.writeHead(400);
      return void res.end();
    }

    if (req.method === 'OPTIONS') {
      res.writeHead(200, { // should be 204, but 200 is most commonly accepted
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Request-Method': 'GET',
        'Access-Control-Max-Age': '864000', // 10 days
        'Content-Type': reqInfo.contentType
      });

      return void res.end();
    } else if (req.method !== 'GET' && req.method !== 'HEAD') {
      this.emit('warn', 'METHOD ' + req.method + ' not allowed');
      res.writeHead(405);
      return void res.end();
    }

    try {
      $this.security.checkSignature(reqInfo.toSign, reqInfo.signature);
    } catch (e){
      $this.emit('error', e);
      if (e instanceof $this.security.SecurityError) {
        res.writeHead(401);
      } else {
        res.writeHead(500);
      }
      return void res.end();
    }

    async.auto({
      getPrefetcher: async.timeout(function getPrefetcher(cb) {
        // acquire prefetcher
        $this.throttle.getPrefetcher(req, cb);
      }, $this.options.stepTimeout),
      requestedImage: ['getPrefetcher', async.timeout(function requestedImage(results, cb) {
        if (!reqInfo.hashFromSteps) {
          // if no steps, continue
          return void cb();
        }

        if (!reqInfo.isCachable) {
          // if skipCache is provided, do not get from storage
          return void cb();
        }

        $this.storage.fetch(req, reqInfo.originalPath, reqInfo.hashFromSteps, function(err, image) {
          if (err) {
            return void cb();
          }

          if (image.info.byteSize !== image.buffer.length) {
            $this.emit('warn', 'requestedImage.byteSize !== buffer.length');
            return void cb();
          }

          cb(null, image);
        });
      }, $this.options.stepTimeout)],
      optimizedOriginalImage: ['requestedImage', async.timeout(function optimizedOriginalImage(results, cb) {
        if (reqInfo.useOriginal || reqInfo.urlInfo.query.useOriginal === 'true') {
          // no need if non-image contentType
          return void cb();
        }
        if (reqInfo.contentType) {
          // no need if non-image contentType
          return void cb();
        }
        if (results.requestedImage) {
          // requested image found, no need for optimized original
          return void cb();
        }
        if (!reqInfo.hashFromOptimizedOriginal) {
          // no optimized original steps, feature disabled
          return void cb();
        }
        if (reqInfo.hashFromSteps === reqInfo.hashFromOptimizedOriginal) {
          // rare chance they're the same, but if so no sense in requesting the same thing
          return void cb();
        }

        $this.storage.fetch(req, reqInfo.originalPath, reqInfo.hashFromOptimizedOriginal, function(err, image) {
          if (err) {
            return void cb();
          }

          if (image.info.byteSize !== image.buffer.length) {
            $this.emit('warn', 'optimizedOriginal.byteSize !== buffer.length');
            return void cb();
          }

          cb(null, image);
        });
      }, $this.options.stepTimeout)],
      originalImage: ['optimizedOriginalImage', async.timeout(function originalImage(results, cb) {
        if (results.requestedImage || results.optimizedOriginalImage) {
          // requested or optimized original image found, original not needed
          return void cb();
        }

        $this.storage.fetch(req, reqInfo.originalPath, null, function(err, image) {
          if (err) {
            return void cb(err);
          }

          cb(null, image);
        });
      }, $this.options.stepTimeout)],
      gzipAsset: ['originalImage', async.timeout(function gzipAsset(results, cb) {
        if (!results.originalImage) {
          // not found, bail out
          return void cb();
        }
        if (!reqInfo.contentType) {
          // no need if image contentType
          return void cb();
        }

        var accept = req.headers['accept-encoding'] || '';
        if (!/gzip/i.test(accept)) {
          // all browsers support gzip, use it or nothing at all
          return void cb();
        }

        zlib.gzip(results.originalImage.buffer, function (err, gzipBuffer) {
          if (err) {
            // if compression fails, log it and move on. no need to fail request
            $this.emit('warn', 'gzip failed, serving original');
            return void cb();
          }

          cb(null, gzipBuffer);
        })
      }, $this.options.stepTimeout)],
      checkContentType: ['originalImage', async.timeout(function checkContentType(results,cb){
        if (reqInfo.useOriginal) {
          helpers.imageType(results.originalImage, function(err, contentType){
            if (err) return void cb(err);
            reqInfo.contentType = contentType;
            cb()
          });
        } else {
          cb();
        }
      }, $this.options.stepTimeout)],
      hasProcessor: ['checkContentType','requestedImage', 'optimizedOriginalImage', 'originalImage', async.timeout(function hasProcessor(results, cb) {
        if (reqInfo.contentType) {
          // no need if non-image contentType
          return void cb(null, false);
        }

        if (results.requestedImage) {
          // no need for processor if desired asset was found
          return void cb(null, false);
        }

        $this.throttle.getProcessor(req, function() {
          cb(null, true); // has processor
        });
      }, $this.options.stepTimeout)],
      processedOptimizedImage: ['hasProcessor', 'optimizedOriginalImage', 'originalImage', async.timeout(function processedOptimizedImage(results, cb) {
        if (!results.hasProcessor) {
          return void cb();
        }
        if (reqInfo.urlInfo.query.useOriginal === 'true') {
          return void cb();
        }
        if (results.optimizedOriginalImage) {
          // already have it
          return void cb();
        }
        if (!reqInfo.hashFromOptimizedOriginal) {
          // no optimized original steps, feature disabled
          return void cb();
        }
        if (reqInfo.hashFromSteps === reqInfo.hashFromOptimizedOriginal) {
          // rare chance they're the same, but if so no sense in requesting the same thing
          return void cb();
        }

        $this.processor.process(results.originalImage, reqInfo.optimizedOriginalSteps, cb);
      }, $this.options.stepTimeout)],
      processedImage: ['hasProcessor', 'processedOptimizedImage', 'originalImage', async.timeout(function processedImage(results, cb) {
        if (!results.hasProcessor) {
          return void cb();
        }

        var original = results.optimizedOriginalImage // 1st priority
            || results.processedOptimizedImage // 2nd priority
            || results.originalImage // last resort
          ;

        $this.processor.process(original, reqInfo.imageSteps, cb);
      }, $this.options.stepTimeout)],
      storeProcessedImage: ['processedImage', async.timeout(function storeProcessedImage(results, cb) {
        // don't wait to respond
        cb();

        if (!results.processedImage) {
          // no processed image to store
          return;
        }

        if (!reqInfo.isCachable) {
          // if skipCache is provided, do not save to storage
          return;
        }

        // track address of author
        results.processedImage.info.author = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

        // store processed image
        $this.storage.store(req, reqInfo.originalPath, reqInfo.hashFromSteps, results.processedImage, function(err) {
          // ignore, error bubbled up through events
        });
      }, $this.options.stepTimeout)],
      storeOptimizedImage: ['processedOptimizedImage', async.timeout(function storeOptimizedImage(results, cb) {
        // don't wait to respond
        cb();

        if (!results.processedOptimizedImage) {
          // no *processed* optimized original image to store
          return;
        }

        if (reqInfo.hashFromSteps === reqInfo.hashFromOptimizedOriginal) {
          // in the off chance the steps are identical, lets not duplicate writes
          return;
        }

        if (!reqInfo.isCachable) {
          // if skipCache is provided, do not save to storage
          return;
        }

        // track address of author
        results.processedOptimizedImage.info.author = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

        // store processed image
        $this.storage.store(req, reqInfo.originalPath, reqInfo.hashFromOptimizedOriginal, results.processedOptimizedImage, function(err) {
          // ignore, error bubbled up through events
        });
      }, $this.options.stepTimeout)]
    }, function(err, results) {
      if (err) {
        err.url = req.url;
        $this.emit('error', err);
        if (err.code && err.code == 'ETIMEDOUT') {
          res.writeHead(408);
        } else if (!results.originalImage && !results.optimizedOriginalImage) {
          res.writeHead(404);
        } else {
          res.writeHead(400);
        }
        return void res.end();
      }

      var image = results.requestedImage
          || results.processedImage
          || results.originalImage
        ;
      if (!image) {
        // should have errored instead of getting this far?
        res.writeHead(404);
        return void res.end();
      }

      var etag = image.ETag;
      var ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch && etag && ifNoneMatch === etag) {
        // not modified, don't resend entire payload
        res.writeHead(304);
        return void res.end();
      }

      var headers = {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Request-Method': 'GET',
        'Access-Control-Max-Age': '864000', // 10 days
        'Cache-Control': 'public,max-age=31536000',
        'Content-Length': (results.gzipAsset && results.gzipAsset.length) || image.buffer.length,
        'Content-Type': reqInfo.contentType || image.contentType
      };

      if (results.gzipAsset) {
        headers['Vary'] = 'Accept-Encoding';
        headers['Content-Encoding'] = 'gzip';
      } else { // image
        headers['Vary'] = 'Accept';
      }

      if (etag) {
        headers['ETag'] = etag;
      }

      if (image.info.width) {
        headers['x-width'] = image.info.width;
      }
      if (image.info.height) {
        headers['x-height'] = image.info.height;
      }

      if (reqInfo.urlInfo.query.download !== undefined) {
        // use filename as the friendly download name, otherwise path
        var fileOrPath = image.info.filename || image.info.path;
        var friendlyName = path.basename(fileOrPath);
        if (image.info.format) {
          // only use this formatting if an image since we can detect format
          var oldExt = path.extname(fileOrPath);
          var newExt = '.' + (image.info.format === 'jpeg' ? 'jpg' : image.info.format);
          friendlyName = path.basename(fileOrPath, oldExt) + newExt;
        }
        headers['Content-Disposition'] = 'attachment; filename="'
          + friendlyName + '"'
        ;
      }

      if (req.method === 'HEAD') {
        res.writeHead(204, headers);

        res.end();
      } else {
        res.writeHead(200, headers);

        res.end(results.gzipAsset || image.buffer);
      }
    });
  };
};
<commit_msg>gracefully handle writeHead failures<commit_after>var path = require('path');
var async = require('async');
var crypto = require('crypto');
var zlib = require('zlib');
var EventEmitter = require('events').EventEmitter;
var Router = require('../router');
var Storage = require('../storage');
var Processor = require('../processor');
var Security = require('../security');
var Throttle = require('./throttle');
var helpers = require('../helpers');

module.exports = Connect;

function Connect(options) {
  if (!(this instanceof Connect)) {
    return new Connect(options);
  }

  EventEmitter.call(this);

  this.options = options || {};
  this.options.stepTimeout = this.options.stepTimeout || 60000;

  var $this = this;
  var errHandler = function(err) {
    $this.emit('error', err);
  };
  var warnHandler = function(err) {
    $this.emit('warn', err);
  };

  this.router = this.options.router instanceof Router
    ? this.options.router
    : new Router(this.options.router)
  ;
  this.router.on('error', errHandler);
  this.router.on('warn', warnHandler);
  this.storage = this.options.storage instanceof Storage
    ? this.options.storage
    : new Storage(this.options.storage)
  ;
  this.storage.on('error', errHandler);
  this.storage.on('warn', warnHandler);
  this.processor = new Processor(this.options.processor);
  this.processor.on('error', errHandler);
  this.processor.on('warn', warnHandler);
  this.throttle = new Throttle(this.options.throttle);
  this.throttle.on('error', errHandler);
  this.throttle.on('warn', warnHandler);
  this.security = new Security(this.options.security);
  this.security.on('error', errHandler);
  this.security.on('warn', warnHandler);
}

var p = Connect.prototype = new EventEmitter();

p.getHandler = function() {
  var $this = this;

  return function(req, res) {
    var safeWriteHead = function () {
      try {
        res.writeHead.apply(res, Array.prototype.slice.call(arguments));
      } catch (ex) {
        $this.emit('warn', ex);
        return false;
      }

      return true;
    };

    if (!$this.throttle.startRequest(req, res)) {
      res.writeHead(503);
      return void res.end();
    }

    if (req.url === '/favicon.ico') {
      // reserved, never valid
      $this.emit('warn', '/favicon.ico not currently supported');
      res.writeHead(404);
      return void res.end();
    }

    var reqInfo;
    try {
      reqInfo = $this.router.getInfo(req);
    } catch (ex) {
      ex.url = req.url;
      $this.emit('error', ex);
      res.writeHead(400);
      return void res.end();
    }

    if (req.method === 'OPTIONS') {
      res.writeHead(200, { // should be 204, but 200 is most commonly accepted
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Request-Method': 'GET',
        'Access-Control-Max-Age': '864000', // 10 days
        'Content-Type': reqInfo.contentType
      });

      return void res.end();
    } else if (req.method !== 'GET' && req.method !== 'HEAD') {
      this.emit('warn', 'METHOD ' + req.method + ' not allowed');
      res.writeHead(405);
      return void res.end();
    }

    try {
      $this.security.checkSignature(reqInfo.toSign, reqInfo.signature);
    } catch (e){
      $this.emit('error', e);
      if (e instanceof $this.security.SecurityError) {
        res.writeHead(401);
      } else {
        res.writeHead(500);
      }
      return void res.end();
    }

    async.auto({
      getPrefetcher: async.timeout(function getPrefetcher(cb) {
        // acquire prefetcher
        $this.throttle.getPrefetcher(req, cb);
      }, $this.options.stepTimeout),
      requestedImage: ['getPrefetcher', async.timeout(function requestedImage(results, cb) {
        if (!reqInfo.hashFromSteps) {
          // if no steps, continue
          return void cb();
        }

        if (!reqInfo.isCachable) {
          // if skipCache is provided, do not get from storage
          return void cb();
        }

        $this.storage.fetch(req, reqInfo.originalPath, reqInfo.hashFromSteps, function(err, image) {
          if (err) {
            return void cb();
          }

          if (image.info.byteSize !== image.buffer.length) {
            $this.emit('warn', 'requestedImage.byteSize !== buffer.length');
            return void cb();
          }

          cb(null, image);
        });
      }, $this.options.stepTimeout)],
      optimizedOriginalImage: ['requestedImage', async.timeout(function optimizedOriginalImage(results, cb) {
        if (reqInfo.useOriginal || reqInfo.urlInfo.query.useOriginal === 'true') {
          // no need if non-image contentType
          return void cb();
        }
        if (reqInfo.contentType) {
          // no need if non-image contentType
          return void cb();
        }
        if (results.requestedImage) {
          // requested image found, no need for optimized original
          return void cb();
        }
        if (!reqInfo.hashFromOptimizedOriginal) {
          // no optimized original steps, feature disabled
          return void cb();
        }
        if (reqInfo.hashFromSteps === reqInfo.hashFromOptimizedOriginal) {
          // rare chance they're the same, but if so no sense in requesting the same thing
          return void cb();
        }

        $this.storage.fetch(req, reqInfo.originalPath, reqInfo.hashFromOptimizedOriginal, function(err, image) {
          if (err) {
            return void cb();
          }

          if (image.info.byteSize !== image.buffer.length) {
            $this.emit('warn', 'optimizedOriginal.byteSize !== buffer.length');
            return void cb();
          }

          cb(null, image);
        });
      }, $this.options.stepTimeout)],
      originalImage: ['optimizedOriginalImage', async.timeout(function originalImage(results, cb) {
        if (results.requestedImage || results.optimizedOriginalImage) {
          // requested or optimized original image found, original not needed
          return void cb();
        }

        $this.storage.fetch(req, reqInfo.originalPath, null, function(err, image) {
          if (err) {
            return void cb(err);
          }

          cb(null, image);
        });
      }, $this.options.stepTimeout)],
      gzipAsset: ['originalImage', async.timeout(function gzipAsset(results, cb) {
        if (!results.originalImage) {
          // not found, bail out
          return void cb();
        }
        if (!reqInfo.contentType) {
          // no need if image contentType
          return void cb();
        }

        var accept = req.headers['accept-encoding'] || '';
        if (!/gzip/i.test(accept)) {
          // all browsers support gzip, use it or nothing at all
          return void cb();
        }

        zlib.gzip(results.originalImage.buffer, function (err, gzipBuffer) {
          if (err) {
            // if compression fails, log it and move on. no need to fail request
            $this.emit('warn', 'gzip failed, serving original');
            return void cb();
          }

          cb(null, gzipBuffer);
        })
      }, $this.options.stepTimeout)],
      checkContentType: ['originalImage', async.timeout(function checkContentType(results,cb){
        if (reqInfo.useOriginal) {
          helpers.imageType(results.originalImage, function(err, contentType){
            if (err) return void cb(err);
            reqInfo.contentType = contentType;
            cb()
          });
        } else {
          cb();
        }
      }, $this.options.stepTimeout)],
      hasProcessor: ['checkContentType','requestedImage', 'optimizedOriginalImage', 'originalImage', async.timeout(function hasProcessor(results, cb) {
        if (reqInfo.contentType) {
          // no need if non-image contentType
          return void cb(null, false);
        }

        if (results.requestedImage) {
          // no need for processor if desired asset was found
          return void cb(null, false);
        }

        $this.throttle.getProcessor(req, function() {
          cb(null, true); // has processor
        });
      }, $this.options.stepTimeout)],
      processedOptimizedImage: ['hasProcessor', 'optimizedOriginalImage', 'originalImage', async.timeout(function processedOptimizedImage(results, cb) {
        if (!results.hasProcessor) {
          return void cb();
        }
        if (reqInfo.urlInfo.query.useOriginal === 'true') {
          return void cb();
        }
        if (results.optimizedOriginalImage) {
          // already have it
          return void cb();
        }
        if (!reqInfo.hashFromOptimizedOriginal) {
          // no optimized original steps, feature disabled
          return void cb();
        }
        if (reqInfo.hashFromSteps === reqInfo.hashFromOptimizedOriginal) {
          // rare chance they're the same, but if so no sense in requesting the same thing
          return void cb();
        }

        $this.processor.process(results.originalImage, reqInfo.optimizedOriginalSteps, cb);
      }, $this.options.stepTimeout)],
      processedImage: ['hasProcessor', 'processedOptimizedImage', 'originalImage', async.timeout(function processedImage(results, cb) {
        if (!results.hasProcessor) {
          return void cb();
        }

        var original = results.optimizedOriginalImage // 1st priority
            || results.processedOptimizedImage // 2nd priority
            || results.originalImage // last resort
          ;

        $this.processor.process(original, reqInfo.imageSteps, cb);
      }, $this.options.stepTimeout)],
      storeProcessedImage: ['processedImage', async.timeout(function storeProcessedImage(results, cb) {
        // don't wait to respond
        cb();

        if (!results.processedImage) {
          // no processed image to store
          return;
        }

        if (!reqInfo.isCachable) {
          // if skipCache is provided, do not save to storage
          return;
        }

        // track address of author
        results.processedImage.info.author = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

        // store processed image
        $this.storage.store(req, reqInfo.originalPath, reqInfo.hashFromSteps, results.processedImage, function(err) {
          // ignore, error bubbled up through events
        });
      }, $this.options.stepTimeout)],
      storeOptimizedImage: ['processedOptimizedImage', async.timeout(function storeOptimizedImage(results, cb) {
        // don't wait to respond
        cb();

        if (!results.processedOptimizedImage) {
          // no *processed* optimized original image to store
          return;
        }

        if (reqInfo.hashFromSteps === reqInfo.hashFromOptimizedOriginal) {
          // in the off chance the steps are identical, lets not duplicate writes
          return;
        }

        if (!reqInfo.isCachable) {
          // if skipCache is provided, do not save to storage
          return;
        }

        // track address of author
        results.processedOptimizedImage.info.author = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

        // store processed image
        $this.storage.store(req, reqInfo.originalPath, reqInfo.hashFromOptimizedOriginal, results.processedOptimizedImage, function(err) {
          // ignore, error bubbled up through events
        });
      }, $this.options.stepTimeout)]
    }, function(err, results) {
      if (err) {
        err.url = req.url;
        $this.emit('error', err);
        if (err.code && err.code == 'ETIMEDOUT') {
          safeWriteHead(408);
        } else if (!results.originalImage && !results.optimizedOriginalImage) {
          safeWriteHead(404);
        } else {
          safeWriteHead(400);
        }
        return void res.end();
      }

      var image = results.requestedImage
          || results.processedImage
          || results.originalImage
        ;
      if (!image) {
        // should have errored instead of getting this far?
        safeWriteHead(404);
        return void res.end();
      }

      var etag = image.ETag;
      var ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch && etag && ifNoneMatch === etag) {
        // not modified, don't resend entire payload
        safeWriteHead(304);
        return void res.end();
      }

      var headers = {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Request-Method': 'GET',
        'Access-Control-Max-Age': '864000', // 10 days
        'Cache-Control': 'public,max-age=31536000',
        'Content-Length': (results.gzipAsset && results.gzipAsset.length) || image.buffer.length,
        'Content-Type': reqInfo.contentType || image.contentType
      };

      if (results.gzipAsset) {
        headers['Vary'] = 'Accept-Encoding';
        headers['Content-Encoding'] = 'gzip';
      } else { // image
        headers['Vary'] = 'Accept';
      }

      if (etag) {
        headers['ETag'] = etag;
      }

      if (image.info.width) {
        headers['x-width'] = image.info.width;
      }
      if (image.info.height) {
        headers['x-height'] = image.info.height;
      }

      if (reqInfo.urlInfo.query.download !== undefined) {
        // use filename as the friendly download name, otherwise path
        var fileOrPath = image.info.filename || image.info.path;
        var friendlyName = path.basename(fileOrPath);
        if (image.info.format) {
          // only use this formatting if an image since we can detect format
          var oldExt = path.extname(fileOrPath);
          var newExt = '.' + (image.info.format === 'jpeg' ? 'jpg' : image.info.format);
          friendlyName = path.basename(fileOrPath, oldExt) + newExt;
        }
        headers['Content-Disposition'] = 'attachment; filename="'
          + friendlyName + '"'
        ;
      }

      if (req.method === 'HEAD') {
        safeWriteHead(204, headers);

        res.end();
      } else {
        safeWriteHead(200, headers);

        res.end(results.gzipAsset || image.buffer);
      }
    });
  };
};

<|endoftext|>