<commit_before>'use strict'

import wildcard from 'socketio-wildcard'
import io from 'socket.io-client'
import Signal from 'signals'
import logger from './logger'
import isNode from './is-node'

const patch = wildcard(io.Manager)

let config = {
  zeroconfName: 'spacebro',
  channelName: null,
  clientName: null,
  packers: [],
  unpackers: [],
  sendBack: true,
  verbose: true,
  multiService: false
}

let connected = false
let unpackers = []
let packers = []
let sockets = []
let events = {}

console.log
const autoconnect = isNode ? require('./autoconnect') : null

function connect (_address, _port, _options) {
  let address = (typeof _address === 'object') ? null : _address
  let port = (typeof _address === 'object') ? null : (typeof _port === 'object') ? null : _port
  let options = (typeof _address === 'object') ? _address : (typeof _port === 'object') ? _port : _options

  Object.assign(config, options)
  logger.setup(config.verbose)
  logger.log('connected with config:\n', config)

  if (address && port) {
    connected = true
    initSocketIO(address, port, null)
  } else if (isNode && address) {
    autoconnect.setup(config.zeroconfName, (addressFound, port) => {
      if (address === addressFound && (!connected || config.multiService)) {
        connected = true
        initSocketIO(address, port)
      } else {
        logger.log(`address found does not match ${address}`)
      }
    })
  } else if (isNode) {
    autoconnect.setup(config.zeroconfName, (addressFound, found) => {
      if (!connected || config.multiService) {
        connected = true
        initSocketIO(addressFound, port)
        logger.log(`connecting to the first ${config.zeroconfName} name we found on ${addressFound}:${port}`)
      } else {
        logger.warn(`multiService is disabled, skipping ${config.zeroconfName} on ${addressFound}:${port}`)
      }
    })
  } else {
    logger.warn('please provide a server address and port')
  }

  for (let packer of config.packers){
    addPacker(packer.handler, packer.priority, packer.eventName)
  }
  for (let unpacker of config.unpackers){
    addUnpacker(unpacker.handler, unpacker.priority, unpacker.eventName)
  }
}

function initSocketIO (address, port) {
  let parsedURI = require('url').parse(address)
  let protocol = parsedURI.protocol ? '' : 'ws://'
  let url = `${protocol}${address}:${port}`

  let socket = io(url)

  patch(socket)

  socket
    .on('connect', function () {
      logger.log('socket connected')
      sockets.push(socket)
      socket.emit('register', {
        clientName: config.clientName,
        channelName: config.channelName
      })
    })
    .on('error', function (err) {
      logger.warn('error', err)
      connected = false
    })
    .on('disconnect', function () {
      logger.log('socket down')
      connected = false
    })
    .on('reconnect', function(){
      logger.log('socket reconnected')
      connected = true
    })
    .on('*', function ({ data }) {
      let [eventName, args] = data
      if (!config.sendBack && args._from === config.clientName) {
        return
      } else if (events[eventName]) {
        logger.log(`socket received ${eventName} with data:`, args)
        for (let unpack of filterHooks(eventName, unpackers)) {
          const unpacked = unpack({ eventName, data: args })
          args = unpacked || args
        }
        events[eventName].dispatch(args)
      }
    })
}

function addPacker (handler, priority, eventName) { addHook(packers, eventName, handler, priority) }
function addUnpacker (handler, priority, eventName) { addHook(unpackers, eventName, handler, priority) }

function emit (eventName, data = {}) {
  if(typeof data !== 'object'){
    data = {data: data}
    data.altered = true
  }
  sendTo(eventName, null, data)
}

function sendTo (eventName, to = null , data = {}) {
  if (connected) {
    data._to = to
    data._from = config.clientName
    for (let pack of filterHooks(eventName, packers)) {
      data = pack({ eventName, data}) || data
    }
    for (let socket of sockets) {
      socket.emit(eventName, data)
    }
  } else {
    logger.warn('not connected')
  }
}

// Reception
function on (eventName, handler, handlerContext, priority) {
  events[eventName] = new Signal()
  events[eventName].add(handler, handlerContext, priority)
}

function once (eventName, handler, handlerContext, priority) {
  events[eventName] = new Signal()
  events[eventName].addOnce(handler, handlerContext, priority)
}

function off (eventName) {
  delete events[eventName]
}

function filterHooks (eventName, hooks) {
  return hooks
    .filter(hook => [eventName, '*'].indexOf(hook.eventName) !== -1)
    .sort(hook => -hook.priority || 0)
    .map(hook => hook.handler)
}

function addHook (hooks, eventName = '*' , handler, priority = 0) {
  hooks.push({ eventName, handler, priority})
}

export default { connect, addPacker, addUnpacker, emit, sendTo, on, once, off }
<commit_msg>:art: Remove useless log<commit_after>'use strict'

import wildcard from 'socketio-wildcard'
import io from 'socket.io-client'
import Signal from 'signals'
import logger from './logger'
import isNode from './is-node'

const patch = wildcard(io.Manager)

let config = {
  zeroconfName: 'spacebro',
  channelName: null,
  clientName: null,
  packers: [],
  unpackers: [],
  sendBack: true,
  verbose: true,
  multiService: false
}

let connected = false
let unpackers = []
let packers = []
let sockets = []
let events = {}

const autoconnect = isNode ? require('./autoconnect') : null

function connect (_address, _port, _options) {
  let address = (typeof _address === 'object') ? null : _address
  let port = (typeof _address === 'object') ? null : (typeof _port === 'object') ? null : _port
  let options = (typeof _address === 'object') ? _address : (typeof _port === 'object') ? _port : _options

  Object.assign(config, options)
  logger.setup(config.verbose)
  logger.log('connected with config:\n', config)

  if (address && port) {
    connected = true
    initSocketIO(address, port, null)
  } else if (isNode && address) {
    autoconnect.setup(config.zeroconfName, (addressFound, port) => {
      if (address === addressFound && (!connected || config.multiService)) {
        connected = true
        initSocketIO(address, port)
      } else {
        logger.log(`address found does not match ${address}`)
      }
    })
  } else if (isNode) {
    autoconnect.setup(config.zeroconfName, (addressFound, found) => {
      if (!connected || config.multiService) {
        connected = true
        initSocketIO(addressFound, port)
        logger.log(`connecting to the first ${config.zeroconfName} name we found on ${addressFound}:${port}`)
      } else {
        logger.warn(`multiService is disabled, skipping ${config.zeroconfName} on ${addressFound}:${port}`)
      }
    })
  } else {
    logger.warn('please provide a server address and port')
  }

  for (let packer of config.packers){
    addPacker(packer.handler, packer.priority, packer.eventName)
  }
  for (let unpacker of config.unpackers){
    addUnpacker(unpacker.handler, unpacker.priority, unpacker.eventName)
  }
}

function initSocketIO (address, port) {
  let parsedURI = require('url').parse(address)
  let protocol = parsedURI.protocol ? '' : 'ws://'
  let url = `${protocol}${address}:${port}`

  let socket = io(url)

  patch(socket)

  socket
    .on('connect', function () {
      logger.log('socket connected')
      sockets.push(socket)
      socket.emit('register', {
        clientName: config.clientName,
        channelName: config.channelName
      })
    })
    .on('error', function (err) {
      logger.warn('error', err)
      connected = false
    })
    .on('disconnect', function () {
      logger.log('socket down')
      connected = false
    })
    .on('reconnect', function(){
      logger.log('socket reconnected')
      connected = true
    })
    .on('*', function ({ data }) {
      let [eventName, args] = data
      if (!config.sendBack && args._from === config.clientName) {
        return
      } else if (events[eventName]) {
        logger.log(`socket received ${eventName} with data:`, args)
        for (let unpack of filterHooks(eventName, unpackers)) {
          const unpacked = unpack({ eventName, data: args })
          args = unpacked || args
        }
        events[eventName].dispatch(args)
      }
    })
}

function addPacker (handler, priority, eventName) { addHook(packers, eventName, handler, priority) }
function addUnpacker (handler, priority, eventName) { addHook(unpackers, eventName, handler, priority) }

function emit (eventName, data = {}) {
  if(typeof data !== 'object'){
    data = {data: data}
    data.altered = true
  }
  sendTo(eventName, null, data)
}

function sendTo (eventName, to = null , data = {}) {
  if (connected) {
    data._to = to
    data._from = config.clientName
    for (let pack of filterHooks(eventName, packers)) {
      data = pack({ eventName, data}) || data
    }
    for (let socket of sockets) {
      socket.emit(eventName, data)
    }
  } else {
    logger.warn('not connected')
  }
}

// Reception
function on (eventName, handler, handlerContext, priority) {
  events[eventName] = new Signal()
  events[eventName].add(handler, handlerContext, priority)
}

function once (eventName, handler, handlerContext, priority) {
  events[eventName] = new Signal()
  events[eventName].addOnce(handler, handlerContext, priority)
}

function off (eventName) {
  delete events[eventName]
}

function filterHooks (eventName, hooks) {
  return hooks
    .filter(hook => [eventName, '*'].indexOf(hook.eventName) !== -1)
    .sort(hook => -hook.priority || 0)
    .map(hook => hook.handler)
}

function addHook (hooks, eventName = '*' , handler, priority = 0) {
  hooks.push({ eventName, handler, priority})
}

export default { connect, addPacker, addUnpacker, emit, sendTo, on, once, off }
<|endoftext|>


<commit_before>var net = require('net');
var tls = require('tls');
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var extend = require('util')._extend;
var util = require('../util');
var config = require('../config');
var rules = require('../rules');
var pluginMgr = require('../plugins');
var serverAgent = require('./util').serverAgent;
var logger = require('../util/logger');
var LOCALHOST = '127.0.0.1';
var ports = {};
var proxy;

function handleWebsocket(socket, clientIp, callback) {
	var wss = clientIp !== false;
	var reqEmitter = new EventEmitter();
	var headers = socket.headers;
	var fullUrl = socket.fullUrl = (wss ? 'wss:' : 'ws:') + '//' + headers.host + socket.url;
	var _rules = socket.rules = rules.resolveRules(fullUrl);
	var filter = socket.filter = rules.resolveFilter(fullUrl);
	var now = Date.now();
	var reqData = {
			ip: util.removeIPV6Prefix(clientIp || socket.remoteAddress),
			method: util.toUpperCase(socket.method) || 'GET',
			httpVersion: socket.httpVersion || '1.1',
            headers: headers
		};
	var resData = {};
	var data = reqEmitter.data = {
			url: fullUrl,
			startTime: now,
			rules: _rules,
			req: reqData,
			res: resData
	};

	var proxyUrl = util.rule.getProxy(_rules.rule);
	var reqSocket, options, pluginHomePage, plugin, pluginRules, matchedUrl, timeout, done;
	plugin = pluginHomePage = pluginMgr.getPluginByHomePage(fullUrl);

	timeout = util.setTimeout(function() {
		destroy(new Error('timeout'));
	});

	function resolveHost(url, callback) {
		if (plugin) {
			return callback(LOCALHOST);
		}
		rules.resolveHost(url, function(err, ip) {
			if (err) {
				return execCallback(err);
			}

			resData.ip = ip;
			data.requestTime = data.dnsTime = Date.now();
			reqEmitter.emit('send', data);
			callback(ip);
		}, pluginRules);
	}

	if (!pluginHomePage && proxyUrl) {
		!filter.hide && proxy.emit('request', reqEmitter);
		var isSocks = /^socks:\/\//.test(proxyUrl);
		data.realUrl = proxyUrl;
		proxyUrl = (wss ? 'wss:' : 'ws:') + util.removeProtocol(proxyUrl);
		resolveHost(proxyUrl, function(ip) {
			options = url.parse(proxyUrl);
			if ((!options.port || options.port == config.port) && util.isLocalAddress(ip)) {
				return execCallback(new Error('Unable to agent to itself (' + ip + ':' + config.port + ')'));
			}
			util.connect({
				isSocks: isSocks,
				host: ip,
				port: options.port,
				isHttps: wss,
				url: fullUrl,
				auth: options.auth,
				headers: {
					host: headers.host,
					'proxy-connection': 'keep-alive',
					'user-agent': headers['user-agent']
				}
			}, function(err, proxySocket) {
				if (err) {
					return execCallback(err);
				}

				reqSocket = proxySocket;
				abortIfUnavailable(reqSocket);
				pipeData();
			});
		});
	} else {
		if (!pluginHomePage) {
			var ruleUrlValue = util.rule.getUrl(_rules.rule);
			var isWebsocket = /^wss?:\/\//.test(ruleUrlValue);
			if (ruleUrlValue != null && !isWebsocket) {
				pluginMgr.getRules(socket, function(rulesMgr, _plugin) {
					plugin = _plugin;
					if (pluginRules = rulesMgr) {
						util.handlePluginRules(socket, rulesMgr);
						if (filter.rule) {
							plugin = null;
						} else {
							var newRules = rulesMgr.resolveRules(fullUrl);
							extend(_rules, newRules);
							if (newRules.rule) {
								plugin = pluginMgr.getPluginByRuleUrl(util.rule.getUrl(newRules.rule));
							}
						}
					}
					connect();
				});
			} else {
				if (isWebsocket) {
					data.realUrl = fullUrl = matchedUrl = ruleUrlValue;
				}
				connect();
			}
		} else {
			connect();
		}
	}

	function connect() {
		options = url.parse(fullUrl);
		!pluginHomePage && !filter.hide && proxy.emit('request', reqEmitter);

		if (plugin) {
			pluginMgr.loadPlugin(plugin, function(err, ports) {
				if (err) {
					return execCallback(err);
				}

				if (pluginHomePage && !ports.uiPort) {
					return execCallback('Not implemented');
				}

				var headers = socket.headers;
				headers[pluginMgr.FULL_URL_HEADER] = encodeURIComponent(fullUrl);
				if (options.protocol == 'wss:') {
					options.protocol = 'ws:';
					headers[pluginMgr.SSL_FLAG_HEADER] = 'true';
				}
				var ruleValue = _rules.rule && util.removeProtocol(_rules.rule.matcher, true);
				if (ruleValue) {
					headers[pluginMgr.RULE_VALUE_HEADER] = encodeURIComponent(ruleValue);
				}
				options.port = pluginHomePage ? ports.uiPort : ports.port;
				if (!pluginHomePage) {
					data.realUrl = util.changePort(fullUrl, options.port);
				}
				_connect();
			});
		} else {
			_connect();
		}
	}

	function _connect() {
		resolveHost(fullUrl, function(ip) {
			var isWss = options.protocol == 'wss:';
			reqSocket = (isWss ? tls : net).connect({
				rejectUnauthorized: false,
				host: ip,
				port: options.port || (isWss ? 443 : 80)
			}, pipeData);
			abortIfUnavailable(reqSocket);
		});
	}

	function pipeData() {
		clearTimeout(timeout);
		var headers = socket.headers;
		var origin;
		if (matchedUrl) {
			headers.host = options.host;
			origin = headers.origin;
			headers.origin = (options.protocol == 'wss:' ? 'https://' : 'http://') + options.host;
		}

		reqSocket.write(socket.getBuffer((plugin || matchedUrl) ? headers : null));
		socket.pipe(reqSocket);
		util.parseReq(reqSocket, function(err, res) {
			if (err) {
				return execCallback(err);
			}

			headers = res.headers;
			if (matchedUrl) {
				headers['access-control-allow-origin'] = origin;
			}
			socket.write(res.getBuffer(matchedUrl ? headers : null));
			res.pipe(socket);
			resData.headers = headers;
			resData.statusCode = res.statusCode;
			reqEmitter.emit('response', data);
			execCallback(null, reqSocket);
		}, true);
	}

	function abortIfUnavailable(socket) {
		return socket.on('error', destroy)
			.on('close', destroy);
	}

	function destroy(err) {
		clearTimeout(timeout);
		socket.destroy();
		reqSocket && reqSocket.destroy();
		execCallback(err);
		var stack = fullUrl;
		if (err) {
			stack += '\n' + err.stack;
		}
		logger.error(stack);
	}

	function execCallback(err, socket) {
		if (done) {
			return;
		}
		done = true;
		data.responseTime = data.endTime = Date.now();
		resData.ip = resData.ip || LOCALHOST;
		if (!err && !socket) {
			err = new Error('aborted');
			data.reqError = true;
			resData.statusCode ='aborted';
			reqData.body = util.getErrorStack(err);
			reqEmitter.emit('abort', data);
		} if (err) {
			data.resError = true;
			resData.statusCode = resData.statusCode || 502;
			resData.body = util.getErrorStack(err);
			util.emitError(reqEmitter, data);
		} else {
			reqEmitter.emit('end', data);
		}

		callback(err, socket);
	}
}

function handleTlsSocket(socket) {
	var reqSocket;
	function destroy(err) {
		socket.destroy();
		reqSocket && reqSocket.destroy();
		logger.error(err);
	}

	function abortIfUnavailable(socket) {
		return socket.on('error', destroy)
			.on('close', destroy);
	}

	abortIfUnavailable(socket);
	util.parseReq(socket, function(err, socket) {
		if (err) {
			return destroy(err);
		}
		//wss
		var clientIp = ports[socket.remotePort];
		var headers = socket.headers;
		if (headers.upgrade && headers.upgrade.toLowerCase() == 'websocket') {
			handleWebsocket(socket, clientIp, function(err, req) {
				if (err) {
					return destroy(err);
				}
				reqSocket = req;
				abortIfUnavailable(reqSocket);
			});
		} else {
			//https
			socket.pause();
			reqSocket = net.connect(config.port, LOCALHOST, function() {
				headers[config.HTTPS_FIELD] = 1;
				headers[config.CLIENT_IP_HEAD] = clientIp;
				reqSocket.write(socket.getBuffer(headers));
				socket.resume();
				socket.pipe(reqSocket).pipe(socket);
	        });
			abortIfUnavailable(reqSocket);
		}
	}, true);

}

module.exports = function dispatch(socket, hostname, _proxy) {
	proxy = _proxy;

	var reqSocket;
	function destroy(err) {
		socket.destroy();
		reqSocket && reqSocket.destroy();
		var stack = hostname;
		if (err) {
			stack += '\n' + err.stack;
		}
		logger.error(stack);
	}

	function abortIfUnavailable(socket) {
		return socket.on('error', destroy)
			.on('close', destroy);
	}

	abortIfUnavailable(socket);
	socket.on('data', request);
	socket.on('end', request);

	function request(chunk) {
		socket.removeListener('data', request);
		socket.removeListener('end', request);
		if (!chunk) {//没有数据
			return destroy();
		}

		if (/upgrade\s*:\s*websocket/i.test(chunk.toString())) { //ws
			util.parseReq(socket, function(err, socket) {
				if (err) {
					return destroy(err);
				}
				handleWebsocket(socket, false, function(err, req) {
					if (err) {
						return destroy(err);
					}
					abortIfUnavailable(reqSocket = req);
				});
			}, chunk, true);
		} else {
			serverAgent.createServer(hostname, handleTlsSocket, function(port) {
				reqSocket = net.connect(port, LOCALHOST, function() {
					ports[reqSocket.localPort] = socket.remoteAddress;
					reqSocket.write(chunk);
		            reqSocket.pipe(socket).pipe(reqSocket);
		        });
				abortIfUnavailable(reqSocket);
			});
		}
	}

};

<commit_msg>feat: Add logger<commit_after>var net = require('net');
var tls = require('tls');
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var extend = require('util')._extend;
var util = require('../util');
var config = require('../config');
var rules = require('../rules');
var pluginMgr = require('../plugins');
var serverAgent = require('./util').serverAgent;
var logger = require('../util/logger');
var LOCALHOST = '127.0.0.1';
var ports = {};
var proxy;

function handleWebsocket(socket, clientIp, callback) {
	var wss = clientIp !== false;
	var reqEmitter = new EventEmitter();
	var headers = socket.headers;
	var fullUrl = socket.fullUrl = (wss ? 'wss:' : 'ws:') + '//' + headers.host + socket.url;
	var _rules = socket.rules = rules.resolveRules(fullUrl);
	var filter = socket.filter = rules.resolveFilter(fullUrl);
	var now = Date.now();
	var reqData = {
			ip: util.removeIPV6Prefix(clientIp || socket.remoteAddress),
			method: util.toUpperCase(socket.method) || 'GET',
			httpVersion: socket.httpVersion || '1.1',
            headers: headers
		};
	var resData = {};
	var data = reqEmitter.data = {
			url: fullUrl,
			startTime: now,
			rules: _rules,
			req: reqData,
			res: resData
	};

	var proxyUrl = util.rule.getProxy(_rules.rule);
	var reqSocket, options, pluginHomePage, plugin, pluginRules, matchedUrl, timeout, done;
	plugin = pluginHomePage = pluginMgr.getPluginByHomePage(fullUrl);

	timeout = util.setTimeout(function() {
		destroy(new Error('timeout'));
	});

	function resolveHost(url, callback) {
		if (plugin) {
			return callback(LOCALHOST);
		}
		rules.resolveHost(url, function(err, ip) {
			if (err) {
				return execCallback(err);
			}

			resData.ip = ip;
			data.requestTime = data.dnsTime = Date.now();
			reqEmitter.emit('send', data);
			callback(ip);
		}, pluginRules);
	}

	if (!pluginHomePage && proxyUrl) {
		!filter.hide && proxy.emit('request', reqEmitter);
		var isSocks = /^socks:\/\//.test(proxyUrl);
		data.realUrl = proxyUrl;
		proxyUrl = (wss ? 'wss:' : 'ws:') + util.removeProtocol(proxyUrl);
		resolveHost(proxyUrl, function(ip) {
			options = url.parse(proxyUrl);
			if ((!options.port || options.port == config.port) && util.isLocalAddress(ip)) {
				return execCallback(new Error('Unable to agent to itself (' + ip + ':' + config.port + ')'));
			}
			util.connect({
				isSocks: isSocks,
				host: ip,
				port: options.port,
				isHttps: wss,
				url: fullUrl,
				auth: options.auth,
				headers: {
					host: headers.host,
					'proxy-connection': 'keep-alive',
					'user-agent': headers['user-agent']
				}
			}, function(err, proxySocket) {
				if (err) {
					return execCallback(err);
				}

				reqSocket = proxySocket;
				abortIfUnavailable(reqSocket);
				pipeData();
			});
		});
	} else {
		if (!pluginHomePage) {
			var ruleUrlValue = util.rule.getUrl(_rules.rule);
			var isWebsocket = /^wss?:\/\//.test(ruleUrlValue);
			if (ruleUrlValue != null && !isWebsocket) {
				pluginMgr.getRules(socket, function(rulesMgr, _plugin) {
					plugin = _plugin;
					if (pluginRules = rulesMgr) {
						util.handlePluginRules(socket, rulesMgr);
						if (filter.rule) {
							plugin = null;
						} else {
							var newRules = rulesMgr.resolveRules(fullUrl);
							extend(_rules, newRules);
							if (newRules.rule) {
								plugin = pluginMgr.getPluginByRuleUrl(util.rule.getUrl(newRules.rule));
							}
						}
					}
					connect();
				});
			} else {
				if (isWebsocket) {
					data.realUrl = fullUrl = matchedUrl = ruleUrlValue;
				}
				connect();
			}
		} else {
			connect();
		}
	}

	function connect() {
		options = url.parse(fullUrl);
		!pluginHomePage && !filter.hide && proxy.emit('request', reqEmitter);

		if (plugin) {
			pluginMgr.loadPlugin(plugin, function(err, ports) {
				if (err) {
					return execCallback(err);
				}

				if (pluginHomePage && !ports.uiPort) {
					return execCallback('Not implemented');
				}

				var headers = socket.headers;
				headers[pluginMgr.FULL_URL_HEADER] = encodeURIComponent(fullUrl);
				if (options.protocol == 'wss:') {
					options.protocol = 'ws:';
					headers[pluginMgr.SSL_FLAG_HEADER] = 'true';
				}
				var ruleValue = _rules.rule && util.removeProtocol(_rules.rule.matcher, true);
				if (ruleValue) {
					headers[pluginMgr.RULE_VALUE_HEADER] = encodeURIComponent(ruleValue);
				}
				options.port = pluginHomePage ? ports.uiPort : ports.port;
				if (!pluginHomePage) {
					data.realUrl = util.changePort(fullUrl, options.port);
				}
				_connect();
			});
		} else {
			_connect();
		}
	}

	function _connect() {
		resolveHost(fullUrl, function(ip) {
			var isWss = options.protocol == 'wss:';
			reqSocket = (isWss ? tls : net).connect({
				rejectUnauthorized: false,
				host: ip,
				port: options.port || (isWss ? 443 : 80)
			}, pipeData);
			abortIfUnavailable(reqSocket);
		});
	}

	function pipeData() {
		clearTimeout(timeout);
		var headers = socket.headers;
		var origin;
		if (matchedUrl) {
			headers.host = options.host;
			origin = headers.origin;
			headers.origin = (options.protocol == 'wss:' ? 'https://' : 'http://') + options.host;
		}

		reqSocket.write(socket.getBuffer((plugin || matchedUrl) ? headers : null));
		socket.pipe(reqSocket);
		util.parseReq(reqSocket, function(err, res) {
			if (err) {
				return execCallback(err);
			}

			headers = res.headers;
			if (matchedUrl) {
				headers['access-control-allow-origin'] = origin;
			}
			socket.write(res.getBuffer(matchedUrl ? headers : null));
			res.pipe(socket);
			resData.headers = headers;
			resData.statusCode = res.statusCode;
			reqEmitter.emit('response', data);
			execCallback(null, reqSocket);
		}, true);
	}

	function abortIfUnavailable(socket) {
		return socket.on('error', destroy)
			.on('close', destroy);
	}

	function destroy(err) {
		clearTimeout(timeout);
		socket.destroy();
		reqSocket && reqSocket.destroy();
		execCallback(err);
		err && logger.error(fullUrl + '\n' + err.stack);
	}

	function execCallback(err, socket) {
		if (done) {
			return;
		}
		done = true;
		data.responseTime = data.endTime = Date.now();
		resData.ip = resData.ip || LOCALHOST;
		if (!err && !socket) {
			err = new Error('aborted');
			data.reqError = true;
			resData.statusCode ='aborted';
			reqData.body = util.getErrorStack(err);
			reqEmitter.emit('abort', data);
		} if (err) {
			data.resError = true;
			resData.statusCode = resData.statusCode || 502;
			resData.body = util.getErrorStack(err);
			util.emitError(reqEmitter, data);
		} else {
			reqEmitter.emit('end', data);
		}

		callback(err, socket);
	}
}

function handleTlsSocket(socket) {
	var reqSocket;
	function destroy(err) {
		socket.destroy();
		reqSocket && reqSocket.destroy();
		logger.error(err);
	}

	function abortIfUnavailable(socket) {
		return socket.on('error', destroy)
			.on('close', destroy);
	}

	abortIfUnavailable(socket);
	util.parseReq(socket, function(err, socket) {
		if (err) {
			return destroy(err);
		}
		//wss
		var clientIp = ports[socket.remotePort];
		var headers = socket.headers;
		if (headers.upgrade && headers.upgrade.toLowerCase() == 'websocket') {
			handleWebsocket(socket, clientIp, function(err, req) {
				if (err) {
					return destroy(err);
				}
				reqSocket = req;
				abortIfUnavailable(reqSocket);
			});
		} else {
			//https
			socket.pause();
			reqSocket = net.connect(config.port, LOCALHOST, function() {
				headers[config.HTTPS_FIELD] = 1;
				headers[config.CLIENT_IP_HEAD] = clientIp;
				reqSocket.write(socket.getBuffer(headers));
				socket.resume();
				socket.pipe(reqSocket).pipe(socket);
	        });
			abortIfUnavailable(reqSocket);
		}
	}, true);

}

module.exports = function dispatch(socket, hostname, _proxy) {
	proxy = _proxy;

	var reqSocket;
	function destroy(err) {
		socket.destroy();
		reqSocket && reqSocket.destroy();
		err && logger.error(hostname + '\n' + err.stack);
	}

	function abortIfUnavailable(socket) {
		return socket.on('error', destroy)
			.on('close', destroy);
	}

	abortIfUnavailable(socket);
	socket.on('data', request);
	socket.on('end', request);

	function request(chunk) {
		socket.removeListener('data', request);
		socket.removeListener('end', request);
		if (!chunk) {//没有数据
			return destroy();
		}

		if (/upgrade\s*:\s*websocket/i.test(chunk.toString())) { //ws
			util.parseReq(socket, function(err, socket) {
				if (err) {
					return destroy(err);
				}
				handleWebsocket(socket, false, function(err, req) {
					if (err) {
						return destroy(err);
					}
					abortIfUnavailable(reqSocket = req);
				});
			}, chunk, true);
		} else {
			serverAgent.createServer(hostname, handleTlsSocket, function(port) {
				reqSocket = net.connect(port, LOCALHOST, function() {
					ports[reqSocket.localPort] = socket.remoteAddress;
					reqSocket.write(chunk);
		            reqSocket.pipe(socket).pipe(reqSocket);
		        });
				abortIfUnavailable(reqSocket);
			});
		}
	}

};

<|endoftext|>


<commit_before>'use strict'

var document = window.document

createDpiTestElements()

module.exports = {
  getValueFromStyle: getValueFromStyle,
  getUnitFromStyle: getUnitFromStyle,
  px2pt: px2pt,
  px2in: px2in,
  in2pt: in2pt,
  getDpi: getDpi
}

/**
 * Get the numerc value of a style value with unit (remove the 2-digits unit and cast as number)
 * For example, returns `11` from a style value of `11px`
 * @method
 * @static
 * @param {string} styleValue A style value with a 2-digits unit
 * @returns {number} - The absolute value of the given style value
 */
function getValueFromStyle (styleValue) {
  return styleValue.slice(0, styleValue.length - 2)
}

/**
 * Get the 2-digit unit representation of a style value with unit.
 * For example, returns `px` from a style value of `11px`
 * @method
 * @static
 * @param {string} styleValue A style value with a 2-digits unit
 * @returns {string} - The unit as a 2-digits representation
 */
function getUnitFromStyle (styleValue) {
  return styleValue.slice(styleValue.length - 2, styleValue.length)
}

/**
 * Converts pixels (px) to points (pt)
 * px -> in -> pt
 * @method
 * @static
 * @param {number} px Number of pixels to convert to points
 * @returns {number} - Resulting number of points (pt)
 */
function px2pt (px) {
  var inches = px2in(px)
  return in2pt(inches)
}

/**
 * Converts pixels (px) to inches (in)
 * dpi = px / in -> in = px / dpi
 * @method
 * @static
 * @param {number} px Number of pixels to convert to inches
 * @returns {number} - Resulting number of inches (in)
 */
function px2in (px) {
  var dpi = getDpi()
  return Number(px) / Number(dpi)
}

/**
 * Converts inches (in) to points (pt)
 * 72 = pt / in -> pt = 72 * in
 * @method
 * @static
 * @param {number} inches Number of inches (in) to convet to points (pt)
 * @returns {number} - Resulting number of points (pt)
 */
function in2pt (inches) {
  return Number(inches) * 72
}

/**
 * Evaluate the DPI of the device's screen (pixels per inche).
 * It creates and inpect a dedicated and hidden `data-dpi-test` DOM element to
 * deduct the screen DPI.
 * @method
 * @static
 * @returns {number} - The current screen DPI, so in pixels per inch.
 */
function getDpi () {
  return document.getElementById('dpi-test').offsetHeight
}

/**
 * @function
 * @inner
 */
function createDpiTestElements () {
  var getDpiHtmlStyle = 'data-dpi-test { height: 1in; left: -100%; position: absolute; top: -100%; width: 1in; }'

  var head = document.getElementsByTagName('head')[0]
  var getDPIElement = document.createElement('style')
  getDPIElement.setAttribute('type', 'text/css')
  getDPIElement.setAttribute('rel', 'stylesheet')
  getDPIElement.innerHTML = getDpiHtmlStyle
  head.appendChild(getDPIElement)

  var body = document.getElementsByTagName('body')[0]
  var dpiTestElement = document.createElement('data-dpi-test')
  dpiTestElement.setAttribute('id', 'dpi-test')
  body.appendChild(dpiTestElement)
}
<commit_msg>add a jsdoc module anotation<commit_after>'use strict'

/**
 * A set of static helper methods to work with units.
 * It is imported from tinymce-plugin-paragraph.
 * @module utils/units
 * @see https://github.com/sirap-group/tinymce-plugin-paragraph
 * @see https://github.com/sirap-group/tinymce-plugin-paragraph/blob/master/src/lib/units.js
 */

var document = window.document

createDpiTestElements()

module.exports = {
  getValueFromStyle: getValueFromStyle,
  getUnitFromStyle: getUnitFromStyle,
  px2pt: px2pt,
  px2in: px2in,
  in2pt: in2pt,
  getDpi: getDpi
}

/**
 * Get the numerc value of a style value with unit (remove the 2-digits unit and cast as number)
 * For example, returns `11` from a style value of `11px`
 * @method
 * @static
 * @param {string} styleValue A style value with a 2-digits unit
 * @returns {number} - The absolute value of the given style value
 */
function getValueFromStyle (styleValue) {
  return styleValue.slice(0, styleValue.length - 2)
}

/**
 * Get the 2-digit unit representation of a style value with unit.
 * For example, returns `px` from a style value of `11px`
 * @method
 * @static
 * @param {string} styleValue A style value with a 2-digits unit
 * @returns {string} - The unit as a 2-digits representation
 */
function getUnitFromStyle (styleValue) {
  return styleValue.slice(styleValue.length - 2, styleValue.length)
}

/**
 * Converts pixels (px) to points (pt)
 * px -> in -> pt
 * @method
 * @static
 * @param {number} px Number of pixels to convert to points
 * @returns {number} - Resulting number of points (pt)
 */
function px2pt (px) {
  var inches = px2in(px)
  return in2pt(inches)
}

/**
 * Converts pixels (px) to inches (in)
 * dpi = px / in -> in = px / dpi
 * @method
 * @static
 * @param {number} px Number of pixels to convert to inches
 * @returns {number} - Resulting number of inches (in)
 */
function px2in (px) {
  var dpi = getDpi()
  return Number(px) / Number(dpi)
}

/**
 * Converts inches (in) to points (pt)
 * 72 = pt / in -> pt = 72 * in
 * @method
 * @static
 * @param {number} inches Number of inches (in) to convet to points (pt)
 * @returns {number} - Resulting number of points (pt)
 */
function in2pt (inches) {
  return Number(inches) * 72
}

/**
 * Evaluate the DPI of the device's screen (pixels per inche).
 * It creates and inpect a dedicated and hidden `data-dpi-test` DOM element to
 * deduct the screen DPI.
 * @method
 * @static
 * @returns {number} - The current screen DPI, so in pixels per inch.
 */
function getDpi () {
  return document.getElementById('dpi-test').offsetHeight
}

/**
 * @function
 * @inner
 */
function createDpiTestElements () {
  var getDpiHtmlStyle = 'data-dpi-test { height: 1in; left: -100%; position: absolute; top: -100%; width: 1in; }'

  var head = document.getElementsByTagName('head')[0]
  var getDPIElement = document.createElement('style')
  getDPIElement.setAttribute('type', 'text/css')
  getDPIElement.setAttribute('rel', 'stylesheet')
  getDPIElement.innerHTML = getDpiHtmlStyle
  head.appendChild(getDPIElement)

  var body = document.getElementsByTagName('body')[0]
  var dpiTestElement = document.createElement('data-dpi-test')
  dpiTestElement.setAttribute('id', 'dpi-test')
  body.appendChild(dpiTestElement)
}
<|endoftext|>


<commit_before>/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $ */

/*
* Displays an auto suggest popup list of files to allow the user to quickly navigate to a file and lines
* within a file.
* Uses FileIndexManger to supply the file list.
*
* TODO (issue 333) - currently jquery smart auto complete is used for the popup list. While it mostly works
* it has several issues, so it should be replace with an alternative. Issues:
* - only accepts an array of strings. A list of objects is preferred to avoid some work arounds to display
*   both the path and filename.
* - the popup position logic has flaws that require css work-arounds
* - the popup properties cannot be modified once the object is constructed
*/

define(function (require, exports, module) {
    'use strict';

    var FileIndexManager    = require("project/FileIndexManager"),
        JSLintUtils         = require("language/JSLintUtils"),
        DocumentManager     = require("document/DocumentManager"),
        EditorManager       = require("editor/EditorManager"),
        CommandManager      = require("command/CommandManager"),
        Commands            = require("command/Commands"),
        QuickOpenJSSymbol   = require("search/QuickOpenJavaScript"),
        QuickOpenCSS        = require("search/QuickOpenCSS"),
        QuickOpenHTML       = require("search/QuickOpenHTML"),
        ProjectManager      = require("project/ProjectManager");

    /** @type Array.<QuickOpenPlugin> */
    var plugins = [];

    /** @type {QuickOpenPlugin} */
    var currentPlugin = null;
    var fileList;

    /**
     * Rembers the current document that was displayed when showDialog() was called
     * The current document is restored if the user presses escape
     * @type {string} fullpath
     */
    var origDocPath;

    /**
     * Rembers the selection in the document origDocPath that was present when showDialog() was called.
     * Focusing on an item can cause the current and and/or selection to change, so this variable restores it.
     * The cursor position is restored if the user presses escape.
     * @type ?{start:{line:number, ch:number}, end:{line:number, ch:number}}
     */
    var origSelection;

    var dialogOpen = false;

    /**
     * Defines API for new QuickOpen plguins
     * @param {string} plugin name
     * @param {Array.<string>} filetypes array. Example: ["js", "css", "txt"]
     * @param {Function} filter takes a query string and returns an array of strings that match the query
     * @param {?Functon} match takes a query string and returns true if this plugin wants to provide results for this query
     * @param {Functon} itemFocus performs an action when a result has focus
     * @param {Functon} itemSelect performs an action when a result is choosen
     * @param {?Functon} resultFormatter takes a query string and an item string and returns a
 item to insert into the displayed search resuklts
     */
    function QuickOpenPlugin(name, fileTypes, filter, match, itemFocus, itemSelect, resultsFormatter) {

        this.name = name;
        this.fileTypes = fileTypes; // empty array indicates all
        this.filter = filter;
        this.match = match;
        this.itemFocus = itemFocus;
        this.itemSelect = itemSelect;
        this.resultsFormatter = resultsFormatter;
    }

    /**
     * Registers new QuickOpenPlugin
     * @param {QuickOpenPlugin} plugin
     */
    function addQuickOpenPlugin(plugin) {
        plugins.push(new QuickOpenPlugin(
            plugin.name,
            plugin.fileTypes,
            plugin.filter,
            plugin.match,
            plugin.itemFocus,
            plugin.itemSelect,
            plugin.resultsFormatter
        ));
    }

    /**
    * QuickNavigateDialog class
    * @constructor
    */
    function QuickNavigateDialog() {
        this.searchField = undefined; // defined when showDialog() is called
    }

    /**
     * Creates a dialog div floating on top of the current code mirror editor
     */
    QuickNavigateDialog.prototype._createDialogDiv = function (template) {
        var wrap = $("#editorHolder")[0];
        this.dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
        this.dialog.className = "CodeMirror-dialog";
        this.dialog.innerHTML = '
' + template + '
';
    };

    function _filenameFromPath(path, includeExtension) {
        var end;
        if (includeExtension) {
            end = path.length;
        } else {
            end = path.lastIndexOf(".");
        }
        return path.slice(path.lastIndexOf("/") + 1, end);
    }

    function extractLineNumber(query) {
        var result;
        var regInfo = query.match(/(!?:)(\d+)/); // colon followed by a digit
        if (regInfo) {
            result = regInfo[2] - 1;
        }

        return result;
    }

    /**
     * Closes the search dialog and resolves the promise that showDialog returned
     */
    QuickNavigateDialog.prototype._handleItemSelect = function (selectedItem) {

        // This is a work-around to default to the first item when a selection event occurs
        // (usually from pressing the enter key) and no item is selected in the list.
        // This is a work-around since  Smart auto complete doesn't select the first item
        if (!selectedItem) {
            selectedItem = $(".smart_autocomplete_container > li:first-child");
        }

        if (currentPlugin) {
            currentPlugin.itemSelect(selectedItem);
        } else {
            var query = this.searchField.val();
            var fullPath = $(selectedItem).attr("data-fullpath");
            fullPath = decodeURIComponent(fullPath);

            // extract line number
            var cursor;
            var gotoLine = extractLineNumber(query);
            if (gotoLine) {
                cursor = {line: gotoLine, ch: 0};
            }

            // Do navigation
            if (fullPath) {
                CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, {fullPath: fullPath})
                    .done(function () {
                        if (gotoLine) {
                            EditorManager.getCurrentFullEditor().setCursorPos(cursor);
                        }
                    });
            } else if (gotoLine) {
                EditorManager.getCurrentFullEditor().setCursorPos(cursor);
            }
        }

        this._close();
        EditorManager.focusEditor();
    };

    /**
     * Opens the file specified by selected item if there is no current plugin, otherwise defers handling
     * to the currentPlugin
     */
    QuickNavigateDialog.prototype._handleItemFocus = function (selectedItem) {
        /*
        Disable opening files on focus for now since this causes focus related bugs between
        the editor and the search field.

        Also, see related code in _handleItemFocus

        if (currentPlugin) {
            currentPlugin.itemFocus(selectedItem);
        } else {
            var fullPath = $(selectedItem).attr("data-fullpath");
            if (fullPath) {
                fullPath = decodeURIComponent(fullPath);
                CommandManager.execute(Commands.FILE_OPEN, {fullPath: fullPath, focusEditor: false});
            }
        }
        */
    };

    QuickNavigateDialog.prototype._handleKeyIn = function (e, query) {
        // extract line number
        var gotoLine = extractLineNumber(query);
        if (gotoLine) {
            var from = {line: gotoLine, ch: 0};
            var to = {line: gotoLine, ch: 99999};

            EditorManager.getCurrentFullEditor().setSelection(from, to);
        }

        // Remove current plugin if the query stops matching
        if (currentPlugin && !currentPlugin.match(query)) {
            currentPlugin = null;
        }

        // Disable focusing on key events for now due to editor focus issues. See _handleItemFocus
        // if ($(".smart_autocomplete_highlight").length === 0) {
        //     this._handleItemFocus($(".smart_autocomplete_container > li:first-child"));
        // }
    };

    /**
     * Close the dialog when the ENTER (13) or ESC (27) key is pressed
     */
    QuickNavigateDialog.prototype._handleKeyDown = function (e) {
        // clear the query on ESC key and restore document and cursor poisition
        if (e.keyCode === 27) {
            e.stopPropagation();
            e.preventDefault();

            if (origDocPath) {
                CommandManager.execute(Commands.FILE_OPEN, {fullPath: origDocPath})
                    .done(function () {
                        if (origSelection) {
                            EditorManager.getCurrentFullEditor().setSelection(origSelection.start, origSelection.end);
                        }
                    });
            }
            this._close();
        }
    };

    /**
    * Closes the search dialog and resolves the promise that showDialog returned
    */
    QuickNavigateDialog.prototype._close = function () {

        if (!dialogOpen) {
            return;
        }
        dialogOpen = false;

        JSLintUtils.setEnabled(true);

        EditorManager.focusEditor();

        // for some odd reason I need to remove the dialog like this through the parent
        // If I do it more directly listeners are not removed by the smart auto complete plugin
        this.dialog.parentNode.removeChild(this.dialog);
        $(".smart_autocomplete_container").remove();

        $(document).off("mousedown", this.handleDocumentClick);
    };

    function filterFileList(query) {
        var filteredList = $.map(fileList, function (itemInfo) {
            // match query against filename only (not the full path)
            var path = itemInfo.fullPath;
            var filename = _filenameFromPath(path, true);
            if (filename.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
                return path;
            } else {
                return null;
            }
        }).sort(function (a, b) {
            a = a.toLowerCase();
            b = b.toLowerCase();
            //first,  sort by filename without extension
            var filenameA = _filenameFromPath(a, false);
            var filenameB = _filenameFromPath(b, false);
            if (filenameA < filenameB) {
                return -1;
            } else if (filenameA > filenameB) {
                return 1;
            } else {
                // filename is the same, compare including extension
                filenameA = _filenameFromPath(a, true);
                filenameB = _filenameFromPath(b, true);
                if (filenameA < filenameB) {
                    return -1;
                } else if (filenameA > filenameB) {
                    return 1;
                } else {
                    return 0;
                }
            }
        });

        return filteredList;
    }

    function _handleFilter(query) {
        var curDoc = DocumentManager.getCurrentDocument();
        if (curDoc) {
            var filename = _filenameFromPath(curDoc.file.fullPath, true);
            var extension = filename.slice(filename.lastIndexOf(".") + 1, filename.length);

            var i;
            for (i = 0; i < plugins.length; i++) {
                var plugin = plugins[i];
                var extensionMatch = plugin.fileTypes.indexOf(extension) !== -1 || plugin.fileTypes.length === 0;
                if (extensionMatch &&  plugin.match && plugin.match(query)) {
                    currentPlugin = plugin;
                    return plugin.filter(query);
                }
            }
        }

        currentPlugin = null;
        return filterFileList(query);
    }

    function defaultResultsFormatter(item, query) {
        query = query.slice(query.indexOf("@") + 1, query.length);
        var boldName = item.replace(new RegExp(query, "gi"), "$&");
        return "
" + boldName + "
";
    }

    function _handleResultsFormatter(item) {
        var query = $('input#quickFileOpenSearch').val();

        if (currentPlugin) {
            var formatter = currentPlugin.resultsFormatter || defaultResultsFormatter;
            return formatter(item, query);
        } else {
            // Format filename result
            var filename = _filenameFromPath(item, true);
            var rPath = ProjectManager.makeProjectRelativeIfPossible(item);
            var boldName = filename.replace(new RegExp(query, "gi"), "$&");
            return "
" + boldName +
                "
" + rPath + "
";
        }
    }

    function setCaretPosition(elem, caretPos) {
        if(elem != null) {
            if(elem.createTextRange) {
                var range = elem.createTextRange();
                range.move('character', caretPos);
                range.select();
            }
            else {
                if(elem.selectionStart) {
                    elem.focus();
                    elem.setSelectionRange(caretPos, caretPos);
                }
                else
                    elem.focus();
            }
        }
    }

    function setSearchFieldValue(initialValue) {
        initialValue = initialValue || "";
        var $field = $('input#quickFileOpenSearch');
        if ($field) {
            $field.val(initialValue);
            setCaretPosition($field.get(0), initialValue.length);
        }
    }

    /**
     * Close the dialog when the user clicks outside of it. Note, auto smart complete has a "lostFocus" event that is
     * supposed to capture this event, but it also gets triggered on keyUp which doesn't work for quick find.
     */
    QuickNavigateDialog.prototype.handleDocumentClick = function(e) {
        if ($(this.dialog).find(e.target).length === 0 && $(".smart_autocomplete_container").find(e.target).length === 0 ) {
            this._close();
        }
    }

    /**
    * Shows the search dialog and initializes the auto suggestion list with filenames from the current project
    */
    QuickNavigateDialog.prototype.showDialog = function (initialValue) {
        var that = this;

        if (dialogOpen) {
            return;
        }
        dialogOpen = true;

        this.handleDocumentClick = this.handleDocumentClick.bind(this);
        $(document).on("mousedown", this.handleDocumentClick);

        // To improve performance during list selection disable JSLint until a document is choosen or dialog is closed
        JSLintUtils.setEnabled(false);

        var curDoc = DocumentManager.getCurrentDocument();
        origDocPath = curDoc ? curDoc.file.fullPath : null;
        if (curDoc) {
            origSelection = EditorManager.getCurrentFullEditor().getSelection();
        } else {
            origSelection = null;
        }

        // Move file list getting code so all this code isn't wrapped in it
        FileIndexManager.getFileInfoList("all")
            .done(function (files) {
                fileList = files;
                var dialogHTML = 'Quick Open: <input type="text" autocomplete="off" id="quickFileOpenSearch" style="width: 30em">';
                that._createDialogDiv(dialogHTML);
                that.searchField = $('input#quickFileOpenSearch');

                that.searchField.smartAutoComplete({
                    source: files,
                    maxResults: 20,
                    //minCharLimit: 0,
                    autocompleteFocused: true,
                    forceSelect: false,
                    typeAhead: false,   // won't work right now because smart auto complete
                                        // using internal raw results instead of filtered results for matching
                    filter: _handleFilter,
                    resultFormatter: _handleResultsFormatter
                });

                that.searchField.bind({
                    itemSelect: function (e, selectedItem) { that._handleItemSelect(selectedItem); },
                    /* Disabling open on rollover right now because it causes bugs
                    itemFocus: function (e, selectedItem) { that._handleItemFocus(selectedItem); },*/
                    keydown: function (e) { that._handleKeyDown(e); },
                    keyIn: function (e, query) { that._handleKeyIn(e, query); },
                    // Note: lostFocus event DOESN'T work because auto smart complete catches the key up from shift-command-o and immediatelly
                    // triggers lostFocus
                });

                setSearchFieldValue(initialValue);
            });
    };

    function doSearch(prefix) {
        prefix = prefix || "";
        var currentEditor = EditorManager.getFocusedEditor();
        var initialString = currentEditor && currentEditor.getSelectedText() || "";
        initialString = prefix + initialString;

        if (dialogOpen) {
            setSearchFieldValue(initialString);
        } else {
            var dialog = new QuickNavigateDialog();
            dialog.showDialog(initialString);
        }
    }

    function doFileSearch() {
        doSearch();
    }

    function doGotoLine() {
        // TODO: Brackets doesn't support disabled menu items right now, when it does goto line and
        // goto definiton should be disabled when there is not a current document
        if (DocumentManager.getCurrentDocument()) {
            doSearch(":");
        }
    }

    // TODO: should provide a way for QuickOpenJSSymbol to create this function as a plugin
    function doDefinitionSearch() {
        if (DocumentManager.getCurrentDocument()) {
            doSearch("@");
        }
    }

    // TODO: in future we would dynamally discover quick open plugins and get their plugins
    var jsFuncPlugin = QuickOpenJSSymbol.getPlugin();
    addQuickOpenPlugin(jsFuncPlugin);

    var cssSelectorsPlugin = QuickOpenCSS.getPlugin();
    addQuickOpenPlugin(cssSelectorsPlugin);

    var htmlIDPlugin = QuickOpenHTML.getPlugin();
    addQuickOpenPlugin(htmlIDPlugin);

    // TODO: allow QuickOpenJS to register it's own commands and keybindings
    CommandManager.register(Commands.NAVIGATE_QUICK_OPEN, doFileSearch);
    CommandManager.register(Commands.NAVIGATE_GOTO_DEFINITION, doDefinitionSearch);
    CommandManager.register(Commands.NAVIGATE_GOTO_LINE, doGotoLine);

    exports.addQuickOpenPlugin = addQuickOpenPlugin;
});
<commit_msg>JSLint Clean up<commit_after>/*
 * Copyright 2011 Adobe Systems Incorporated. All Rights Reserved.
 */

/*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $ */

/*
* Displays an auto suggest popup list of files to allow the user to quickly navigate to a file and lines
* within a file.
* Uses FileIndexManger to supply the file list.
*
* TODO (issue 333) - currently jquery smart auto complete is used for the popup list. While it mostly works
* it has several issues, so it should be replace with an alternative. Issues:
* - only accepts an array of strings. A list of objects is preferred to avoid some work arounds to display
*   both the path and filename.
* - the popup position logic has flaws that require css work-arounds
* - the popup properties cannot be modified once the object is constructed
*/

define(function (require, exports, module) {
    'use strict';

    var FileIndexManager    = require("project/FileIndexManager"),
        JSLintUtils         = require("language/JSLintUtils"),
        DocumentManager     = require("document/DocumentManager"),
        EditorManager       = require("editor/EditorManager"),
        CommandManager      = require("command/CommandManager"),
        Commands            = require("command/Commands"),
        QuickOpenJSSymbol   = require("search/QuickOpenJavaScript"),
        QuickOpenCSS        = require("search/QuickOpenCSS"),
        QuickOpenHTML       = require("search/QuickOpenHTML"),
        ProjectManager      = require("project/ProjectManager");

    /** @type Array.<QuickOpenPlugin> */
    var plugins = [];

    /** @type {QuickOpenPlugin} */
    var currentPlugin = null;
    var fileList;

    /**
     * Rembers the current document that was displayed when showDialog() was called
     * The current document is restored if the user presses escape
     * @type {string} fullpath
     */
    var origDocPath;

    /**
     * Rembers the selection in the document origDocPath that was present when showDialog() was called.
     * Focusing on an item can cause the current and and/or selection to change, so this variable restores it.
     * The cursor position is restored if the user presses escape.
     * @type ?{start:{line:number, ch:number}, end:{line:number, ch:number}}
     */
    var origSelection;

    var dialogOpen = false;

    /**
     * Defines API for new QuickOpen plguins
     * @param {string} plugin name
     * @param {Array.<string>} filetypes array. Example: ["js", "css", "txt"]
     * @param {Function} filter takes a query string and returns an array of strings that match the query
     * @param {?Functon} match takes a query string and returns true if this plugin wants to provide results for this query
     * @param {Functon} itemFocus performs an action when a result has focus
     * @param {Functon} itemSelect performs an action when a result is choosen
     * @param {?Functon} resultFormatter takes a query string and an item string and returns a
 item to insert into the displayed search resuklts
     */
    function QuickOpenPlugin(name, fileTypes, filter, match, itemFocus, itemSelect, resultsFormatter) {

        this.name = name;
        this.fileTypes = fileTypes; // empty array indicates all
        this.filter = filter;
        this.match = match;
        this.itemFocus = itemFocus;
        this.itemSelect = itemSelect;
        this.resultsFormatter = resultsFormatter;
    }

    /**
     * Registers new QuickOpenPlugin
     * @param {QuickOpenPlugin} plugin
     */
    function addQuickOpenPlugin(plugin) {
        plugins.push(new QuickOpenPlugin(
            plugin.name,
            plugin.fileTypes,
            plugin.filter,
            plugin.match,
            plugin.itemFocus,
            plugin.itemSelect,
            plugin.resultsFormatter
        ));
    }

    /**
    * QuickNavigateDialog class
    * @constructor
    */
    function QuickNavigateDialog() {
        this.searchField = undefined; // defined when showDialog() is called
    }

    /**
     * Creates a dialog div floating on top of the current code mirror editor
     */
    QuickNavigateDialog.prototype._createDialogDiv = function (template) {
        var wrap = $("#editorHolder")[0];
        this.dialog = wrap.insertBefore(document.createElement("div"), wrap.firstChild);
        this.dialog.className = "CodeMirror-dialog";
        this.dialog.innerHTML = '
' + template + '
';
    };

    function _filenameFromPath(path, includeExtension) {
        var end;
        if (includeExtension) {
            end = path.length;
        } else {
            end = path.lastIndexOf(".");
        }
        return path.slice(path.lastIndexOf("/") + 1, end);
    }

    function extractLineNumber(query) {
        var result;
        var regInfo = query.match(/(!?:)(\d+)/); // colon followed by a digit
        if (regInfo) {
            result = regInfo[2] - 1;
        }

        return result;
    }

    /**
     * Closes the search dialog and resolves the promise that showDialog returned
     */
    QuickNavigateDialog.prototype._handleItemSelect = function (selectedItem) {

        // This is a work-around to default to the first item when a selection event occurs
        // (usually from pressing the enter key) and no item is selected in the list.
        // This is a work-around since  Smart auto complete doesn't select the first item
        if (!selectedItem) {
            selectedItem = $(".smart_autocomplete_container > li:first-child");
        }

        if (currentPlugin) {
            currentPlugin.itemSelect(selectedItem);
        } else {
            var query = this.searchField.val();
            var fullPath = $(selectedItem).attr("data-fullpath");
            fullPath = decodeURIComponent(fullPath);

            // extract line number
            var cursor;
            var gotoLine = extractLineNumber(query);
            if (gotoLine) {
                cursor = {line: gotoLine, ch: 0};
            }

            // Do navigation
            if (fullPath) {
                CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, {fullPath: fullPath})
                    .done(function () {
                        if (gotoLine) {
                            EditorManager.getCurrentFullEditor().setCursorPos(cursor);
                        }
                    });
            } else if (gotoLine) {
                EditorManager.getCurrentFullEditor().setCursorPos(cursor);
            }
        }

        this._close();
        EditorManager.focusEditor();
    };

    /**
     * Opens the file specified by selected item if there is no current plugin, otherwise defers handling
     * to the currentPlugin
     */
    QuickNavigateDialog.prototype._handleItemFocus = function (selectedItem) {
        /*
        Disable opening files on focus for now since this causes focus related bugs between
        the editor and the search field.

        Also, see related code in _handleItemFocus

        if (currentPlugin) {
            currentPlugin.itemFocus(selectedItem);
        } else {
            var fullPath = $(selectedItem).attr("data-fullpath");
            if (fullPath) {
                fullPath = decodeURIComponent(fullPath);
                CommandManager.execute(Commands.FILE_OPEN, {fullPath: fullPath, focusEditor: false});
            }
        }
        */
    };

    QuickNavigateDialog.prototype._handleKeyIn = function (e, query) {
        // extract line number
        var gotoLine = extractLineNumber(query);
        if (gotoLine) {
            var from = {line: gotoLine, ch: 0};
            var to = {line: gotoLine, ch: 99999};

            EditorManager.getCurrentFullEditor().setSelection(from, to);
        }

        // Remove current plugin if the query stops matching
        if (currentPlugin && !currentPlugin.match(query)) {
            currentPlugin = null;
        }

        // Disable focusing on key events for now due to editor focus issues. See _handleItemFocus
        // if ($(".smart_autocomplete_highlight").length === 0) {
        //     this._handleItemFocus($(".smart_autocomplete_container > li:first-child"));
        // }
    };

    /**
     * Close the dialog when the ENTER (13) or ESC (27) key is pressed
     */
    QuickNavigateDialog.prototype._handleKeyDown = function (e) {
        // clear the query on ESC key and restore document and cursor poisition
        if (e.keyCode === 27) {
            e.stopPropagation();
            e.preventDefault();

            if (origDocPath) {
                CommandManager.execute(Commands.FILE_OPEN, {fullPath: origDocPath})
                    .done(function () {
                        if (origSelection) {
                            EditorManager.getCurrentFullEditor().setSelection(origSelection.start, origSelection.end);
                        }
                    });
            }
            this._close();
        }
    };

    /**
    * Closes the search dialog and resolves the promise that showDialog returned
    */
    QuickNavigateDialog.prototype._close = function () {

        if (!dialogOpen) {
            return;
        }
        dialogOpen = false;

        JSLintUtils.setEnabled(true);

        EditorManager.focusEditor();

        // for some odd reason I need to remove the dialog like this through the parent
        // If I do it more directly listeners are not removed by the smart auto complete plugin
        this.dialog.parentNode.removeChild(this.dialog);
        $(".smart_autocomplete_container").remove();

        $(document).off("mousedown", this.handleDocumentClick);
    };

    function filterFileList(query) {
        var filteredList = $.map(fileList, function (itemInfo) {
            // match query against filename only (not the full path)
            var path = itemInfo.fullPath;
            var filename = _filenameFromPath(path, true);
            if (filename.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
                return path;
            } else {
                return null;
            }
        }).sort(function (a, b) {
            a = a.toLowerCase();
            b = b.toLowerCase();
            //first,  sort by filename without extension
            var filenameA = _filenameFromPath(a, false);
            var filenameB = _filenameFromPath(b, false);
            if (filenameA < filenameB) {
                return -1;
            } else if (filenameA > filenameB) {
                return 1;
            } else {
                // filename is the same, compare including extension
                filenameA = _filenameFromPath(a, true);
                filenameB = _filenameFromPath(b, true);
                if (filenameA < filenameB) {
                    return -1;
                } else if (filenameA > filenameB) {
                    return 1;
                } else {
                    return 0;
                }
            }
        });

        return filteredList;
    }

    function _handleFilter(query) {
        var curDoc = DocumentManager.getCurrentDocument();
        if (curDoc) {
            var filename = _filenameFromPath(curDoc.file.fullPath, true);
            var extension = filename.slice(filename.lastIndexOf(".") + 1, filename.length);

            var i;
            for (i = 0; i < plugins.length; i++) {
                var plugin = plugins[i];
                var extensionMatch = plugin.fileTypes.indexOf(extension) !== -1 || plugin.fileTypes.length === 0;
                if (extensionMatch &&  plugin.match && plugin.match(query)) {
                    currentPlugin = plugin;
                    return plugin.filter(query);
                }
            }
        }

        currentPlugin = null;
        return filterFileList(query);
    }

    function defaultResultsFormatter(item, query) {
        query = query.slice(query.indexOf("@") + 1, query.length);
        var boldName = item.replace(new RegExp(query, "gi"), "$&");
        return "
" + boldName + "
";
    }

    function _handleResultsFormatter(item) {
        var query = $('input#quickFileOpenSearch').val();

        if (currentPlugin) {
            var formatter = currentPlugin.resultsFormatter || defaultResultsFormatter;
            return formatter(item, query);
        } else {
            // Format filename result
            var filename = _filenameFromPath(item, true);
            var rPath = ProjectManager.makeProjectRelativeIfPossible(item);
            var boldName = filename.replace(new RegExp(query, "gi"), "$&");
            return "
" + boldName +
                "
" + rPath + "
";
        }
    }

    function setCaretPosition(elem, caretPos) {
        if (elem !== null) {
            if (elem.createTextRange) {
                var range = elem.createTextRange();
                range.move('character', caretPos);
                range.select();
            } else {
                if (elem.selectionStart) {
                    elem.focus();
                    elem.setSelectionRange(caretPos, caretPos);
                } else {
                    elem.focus();
                }
            }
        }
    }

    function setSearchFieldValue(initialValue) {
        initialValue = initialValue || "";
        var $field = $('input#quickFileOpenSearch');
        if ($field) {
            $field.val(initialValue);
            setCaretPosition($field.get(0), initialValue.length);
        }
    }

    /**
     * Close the dialog when the user clicks outside of it. Note, auto smart complete has a "lostFocus" event that is
     * supposed to capture this event, but it also gets triggered on keyUp which doesn't work for quick find.
     */
    QuickNavigateDialog.prototype.handleDocumentClick = function (e) {
        if ($(this.dialog).find(e.target).length === 0 && $(".smart_autocomplete_container").find(e.target).length === 0) {
            this._close();
        }
    };

    /**
    * Shows the search dialog and initializes the auto suggestion list with filenames from the current project
    */
    QuickNavigateDialog.prototype.showDialog = function (initialValue) {
        var that = this;

        if (dialogOpen) {
            return;
        }
        dialogOpen = true;

        this.handleDocumentClick = this.handleDocumentClick.bind(this);
        $(document).on("mousedown", this.handleDocumentClick);

        // To improve performance during list selection disable JSLint until a document is choosen or dialog is closed
        JSLintUtils.setEnabled(false);

        var curDoc = DocumentManager.getCurrentDocument();
        origDocPath = curDoc ? curDoc.file.fullPath : null;
        if (curDoc) {
            origSelection = EditorManager.getCurrentFullEditor().getSelection();
        } else {
            origSelection = null;
        }

        // Move file list getting code so all this code isn't wrapped in it
        FileIndexManager.getFileInfoList("all")
            .done(function (files) {
                fileList = files;
                var dialogHTML = 'Quick Open: <input type="text" autocomplete="off" id="quickFileOpenSearch" style="width: 30em">';
                that._createDialogDiv(dialogHTML);
                that.searchField = $('input#quickFileOpenSearch');

                that.searchField.smartAutoComplete({
                    source: files,
                    maxResults: 20,
                    //minCharLimit: 0,
                    autocompleteFocused: true,
                    forceSelect: false,
                    typeAhead: false,   // won't work right now because smart auto complete
                                        // using internal raw results instead of filtered results for matching
                    filter: _handleFilter,
                    resultFormatter: _handleResultsFormatter
                });

                that.searchField.bind({
                    itemSelect: function (e, selectedItem) { that._handleItemSelect(selectedItem); },
                    /* Disabling open on rollover right now because it causes bugs
                    itemFocus: function (e, selectedItem) { that._handleItemFocus(selectedItem); },*/
                    keydown: function (e) { that._handleKeyDown(e); },
                    keyIn: function (e, query) { that._handleKeyIn(e, query); }
                    // Note: lostFocus event DOESN'T work because auto smart complete catches the key up from shift-command-o and immediatelly
                    // triggers lostFocus
                });

                setSearchFieldValue(initialValue);
            });
    };

    function doSearch(prefix) {
        prefix = prefix || "";
        var currentEditor = EditorManager.getFocusedEditor();
        var initialString = (currentEditor && currentEditor.getSelectedText()) || "";
        initialString = prefix + initialString;

        if (dialogOpen) {
            setSearchFieldValue(initialString);
        } else {
            var dialog = new QuickNavigateDialog();
            dialog.showDialog(initialString);
        }
    }

    function doFileSearch() {
        doSearch();
    }

    function doGotoLine() {
        // TODO: Brackets doesn't support disabled menu items right now, when it does goto line and
        // goto definiton should be disabled when there is not a current document
        if (DocumentManager.getCurrentDocument()) {
            doSearch(":");
        }
    }

    // TODO: should provide a way for QuickOpenJSSymbol to create this function as a plugin
    function doDefinitionSearch() {
        if (DocumentManager.getCurrentDocument()) {
            doSearch("@");
        }
    }

    // TODO: in future we would dynamally discover quick open plugins and get their plugins
    var jsFuncPlugin = QuickOpenJSSymbol.getPlugin();
    addQuickOpenPlugin(jsFuncPlugin);

    var cssSelectorsPlugin = QuickOpenCSS.getPlugin();
    addQuickOpenPlugin(cssSelectorsPlugin);

    var htmlIDPlugin = QuickOpenHTML.getPlugin();
    addQuickOpenPlugin(htmlIDPlugin);

    // TODO: allow QuickOpenJS to register it's own commands and keybindings
    CommandManager.register(Commands.NAVIGATE_QUICK_OPEN, doFileSearch);
    CommandManager.register(Commands.NAVIGATE_GOTO_DEFINITION, doDefinitionSearch);
    CommandManager.register(Commands.NAVIGATE_GOTO_LINE, doGotoLine);

    exports.addQuickOpenPlugin = addQuickOpenPlugin;
});
<|endoftext|>


<commit_before>var sys = require('sys'),
  fs = require('fs'),
  View = require('view').View,
  Metric = require('metric').Metric,
  Aggregates = require('aggregates').Aggregates,
  Buffer = require('buffer').Buffer,
  io = require('socket.io'),
  arrays = require('deps/arrays'),
  querystring = require('querystring');

try {
  var configJSON = fs.readFileSync(__dirname + "/../config/app.json");
} catch(e) {
  sys.log("File config/app.json not found.  Try: `cp config/app.json.sample config/app.json`");
}
var config = JSON.parse(configJSON.toString());

var Hummingbird = function(db, callback) {
  var pixelData = fs.readFileSync(__dirname + "/../images/tracking.gif", 'binary');
  this.pixel = new Buffer(43);
  this.pixel.write(pixelData, 'binary', 0);

  this.metrics = [];
};

Hummingbird.prototype = {
  init: function(db, callback) {
    this.setupDb(db, function() {
      callback();
    });
  },

  setupDb: function(db, callback) {
    var self = this;
    db.createCollection('visits', function(err, collection) {
      db.collection('visits', function(err, collection) {
        self.collection = collection;
        callback();
      });
    });
  },

  addAllMetrics: function(socket, db) {
    var self = this;

    Metric.allMetrics(function(metric) {
      metric.init(db);
      metric.socket = socket;
      self.metrics.push(metric);
    });
  },

  serveRequest: function(req, res) {
    this.writePixel(res);

    var env = this.splitQuery(req.url.split('?')[1]);
    env.timestamp = new Date();
    // sys.log(JSON.stringify(env, null, 2));

    env.ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    var view = new View(env);

    env.url_key = view.urlKey;
    env.product_id = view.productId;

    this.collection.insertAll([env]);

    for(var i = 0; i < this.metrics.length; i++) {
      this.metrics[i].incrementCallback(view);
      this.metrics[i].isDirty = true;
    }
  },

  splitQuery: function(query) {
    var queryString = {};
    (query || "").replace(
      new RegExp("([^?=&]+)(=([^&]*))?", "g"),
      function($0, $1, $2, $3) { queryString[$1] = querystring.unescape($3.replace(/\+/g, ' ')); }
    );

    return queryString;
  },

  writePixel: function(res) {
    res.writeHead(200, { 'Content-Type': 'image/gif',
                         'Content-Disposition': 'inline',
                         'Content-Length': '43' });
    res.end(this.pixel);
  },

  handleError: function(req, res, e) {
    res.writeHead(500, {});
    res.write("Server error");
    res.close();

    e.stack = e.stack.split('\n');
    e.url = req.url;
    sys.log(JSON.stringify(e, null, 2));
  }
};

exports.Hummingbird = Hummingbird;
<commit_msg>refactor insertData into its own function in preparation for udp support<commit_after>var sys = require('sys'),
  fs = require('fs'),
  View = require('view').View,
  Metric = require('metric').Metric,
  Aggregates = require('aggregates').Aggregates,
  Buffer = require('buffer').Buffer,
  io = require('socket.io'),
  arrays = require('deps/arrays'),
  querystring = require('querystring');

try {
  var configJSON = fs.readFileSync(__dirname + "/../config/app.json");
} catch(e) {
  sys.log("File config/app.json not found.  Try: `cp config/app.json.sample config/app.json`");
}
var config = JSON.parse(configJSON.toString());

var Hummingbird = function(db, callback) {
  var pixelData = fs.readFileSync(__dirname + "/../images/tracking.gif", 'binary');
  this.pixel = new Buffer(43);
  this.pixel.write(pixelData, 'binary', 0);

  this.metrics = [];
};

Hummingbird.prototype = {
  init: function(db, callback) {
    this.setupDb(db, function() {
      callback();
    });
  },

  setupDb: function(db, callback) {
    var self = this;
    db.createCollection('visits', function(err, collection) {
      db.collection('visits', function(err, collection) {
        self.collection = collection;
        callback();
      });
    });
  },

  addAllMetrics: function(socket, db) {
    var self = this;

    Metric.allMetrics(function(metric) {
      metric.init(db);
      metric.socket = socket;
      self.metrics.push(metric);
    });
  },

  serveRequest: function(req, res) {
    this.writePixel(res);

    var env = this.splitQuery(req.url.split('?')[1]);
    env.ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

    this.insertData(env);
  },

  insertData: function(env) {
    env.timestamp = new Date();
    var view = new View(env);

    env.url_key = view.urlKey;
    env.product_id = view.productId;

    this.collection.insertAll([env]);

    for(var i = 0; i < this.metrics.length; i++) {
      this.metrics[i].incrementCallback(view);
      this.metrics[i].isDirty = true;
    }
  },

  splitQuery: function(query) {
    var queryString = {};
    (query || "").replace(
      new RegExp("([^?=&]+)(=([^&]*))?", "g"),
      function($0, $1, $2, $3) { queryString[$1] = querystring.unescape($3.replace(/\+/g, ' ')); }
    );

    return queryString;
  },

  writePixel: function(res) {
    res.writeHead(200, { 'Content-Type': 'image/gif',
                         'Content-Disposition': 'inline',
                         'Content-Length': '43' });
    res.end(this.pixel);
  },

  handleError: function(req, res, e) {
    res.writeHead(500, {});
    res.write("Server error");
    res.end();

    e.stack = e.stack.split('\n');
    e.url = req.url;
    sys.log(JSON.stringify(e, null, 2));
  }
};

exports.Hummingbird = Hummingbird;
<|endoftext|>


<commit_before>export function truncate(str, length) {
  if (!str || typeof str !== 'string' || str.length <= length) {
    return str;
  }
  const subString = str.substr(0, length-1);
  return `${subString.substr(0, subString.lastIndexOf(' '))} …`;
}

export function trimObject(obj) {
  const res = {};
  Object.keys(obj).forEach(attr => {
    if (typeof obj[attr] !== 'undefined') {
      res[attr] = obj[attr];
    }
  });
  return res;
}

/**
 * Gives the number of days between two dates
 */
export const days = (d1, d2 = new Date) => {
  const oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
  return Math.round(Math.abs((new Date(d1).getTime() - new Date(d2).getTime())/(oneDay)));
}

export function filterCollection(array, cond, inverse) {
  if (!array || !cond) return array;

  const test = (obj, cond, depth = 0) => {
    if (depth > 5) return false;
    if (!obj) return false;
    if (cond instanceof RegExp)
      return Boolean(obj.match(cond));
    if (typeof cond === 'string')
      return obj === cond;

    const nextKey = Object.keys(cond)[0];
    return test(obj[nextKey], cond[nextKey], ++depth);
  }

  return array.filter((r) => inverse ? !test(r, cond) : test(r, cond))
}

export const isValidUrl = (url) => {
  if (typeof url !== 'string') return false;
  return Boolean(url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/));
}

export const isValidEmail = (email) => {
  if (typeof email !== 'string') return false;
  return email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
};

export function getCurrencySymbol(currency) {
 const r = Number(0).toLocaleString(currency, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 0});
 return r.replace(/0$/,'');
}

export function resizeImage(imageUrl, { width, height, query, baseUrl }) {
  if (!imageUrl) return null;
  if (!query && imageUrl.match(/\.svg$/)) return imageUrl; // if we don't need to transform the image, no need to proxy it.
  let queryurl = '';
  if (query) {
    queryurl = encodeURIComponent(query);
  } else {
    if (width) queryurl += `&width=${width}`;
    if (height) queryurl += `&height=${height}`;
  }
  return `${process.env.IMAGES_URL || baseUrl || ''}/proxy/images/?src=${encodeURIComponent(imageUrl)}${queryurl}`;
}

export function imagePreview(src, defaultImage, options = { width: 640 }) {

  if (typeof options.width === 'string') {
    options.width = Number(options.width.replace(/rem/,'')) * 10;
  }
  if (typeof options.height === 'string') {
    options.height = Number(options.height.replace(/rem/,'')) * 10;
  }

  if (src) return resizeImage(src, options);
  return defaultImage;
}

export function prettyUrl(url) {
  if (!url) return '';
  return url.replace(/^https?:\/\/(www\.)?/i,'').replace(/\?.+/, '').replace(/\/$/,'');
}

function getLocaleFromCurrency(currency) {
  let locale;
  switch (currency) {
    case 'USD':
      locale = 'en-US';
      break;
    case 'EUR':
      locale = 'en-EU';
      break;
    default:
      locale = currency;
  }
  return locale;
}

export function getQueryParams() {
  const urlParams = {};
  let match;
  const pl     = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) {
 return decodeURIComponent(s.replace(pl, " "));
},
        query  = window.location.search.substring(1);

  // eslint-disable-next-line no-cond-assign
  while (match = search.exec(query)) {
    urlParams[decode(match[1])] = decode(match[2]);
  }
  return urlParams;
}

// source: https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string
function extractHostname(url) {
  let hostname;
  //find & remove protocol (http, ftp, etc.) and get hostname

  if (url.indexOf("://") > -1) {
      hostname = url.split('/')[2];
  } else {
      hostname = url.split('/')[0];
  }

  //find & remove port number
  hostname = hostname.split(':')[0];
  //find & remove "?"
  hostname = hostname.split('?')[0];

  return hostname;
}

export function getDomain(url = '') {
  let domain = extractHostname(url);
  const splitArr = domain.split('.'), arrLen = splitArr.length;

  //extracting the root domain here
  //if there is a subdomain
  if (arrLen > 2) {
      domain = `${splitArr[arrLen - 2]  }.${  splitArr[arrLen - 1]}`;
      //check to see if it's using a Country Code Top Level Domain (ccTLD) (i.e. ".me.uk")
      if (splitArr[arrLen - 1].length == 2 && splitArr[arrLen - 1].length == 2) {
          //this is using a ccTLD
          domain = `${splitArr[arrLen - 3]  }.${  domain}`;
      }
  }
  return domain;
}

export function formatDate(date, options = { month: 'long', year: 'numeric' }) {
  const d = new Date(date);
  const locale = (typeof window !== 'undefined') ? window.navigator.language : options.locale || 'en-US';
  return d.toLocaleDateString(locale, options);
}

export function formatCurrency(amount, currency = 'USD', options = {}) {
  amount = amount / 100;

  let minimumFractionDigits = 2;
  let maximumFractionDigits = 2;

  if (options.hasOwnProperty('minimumFractionDigits')) {
    minimumFractionDigits = options.minimumFractionDigits
  } else if (options.hasOwnProperty('precision')) {
    minimumFractionDigits = options.precision;
    maximumFractionDigits = options.precision;
  }

  return amount.toLocaleString(getLocaleFromCurrency(currency), {
    style: 'currency',
    currency,
    minimumFractionDigits : minimumFractionDigits,
    maximumFractionDigits : maximumFractionDigits
  })
}

export const singular = (str) => {
  if (!str) return '';
  return str.replace(/ies$/,'y').replace(/s$/,'');
}

export const pluralize = (str, n) => {
  return (n > 1) ? `${str}s` : str;
}

export const translateApiUrl = (url) => {
  const withoutParams = process.env.API_URL + (url.replace('/api/', '/'));
  const hasParams = `${url}`.match(/\?/)
  if (process.env.API_KEY) {
    return `${withoutParams}${hasParams ? '&' : '?'}api_key=${process.env.API_KEY}`;
  } else {
    return withoutParams;
  }
};

export const capitalize = (str) => {
  if (typeof str !== 'string') return '';
  str = str.trim();
  if (str.length === 0) return '';
  return `${str[0].toUpperCase()}${str.substr(1)}`;
}

export const trim = (str, length) => {
  if (!str) return '';

  if (str.length <= length) return str;

  const res = [];
  let res_length = 0;
  const words = str.split(' ');
  let i=0;
  while (res_length < length && i < words.length) {
    const w = words[i++];
    res_length += w.length + 1;
    res.push(w);
  }
  return `${res.join(' ')} …`;
}

export const firstSentence = (str, length) => {
  if (!str) return '';

  str = str.replace(/&/g, '&');

  if (str.length <= length) return str;
  const tokens = str.match(/\.|\?|!/);
  if (tokens) {
    str = str.substr(0, tokens.index + 1);
  }
  str = trim(str, length);
  return str;
}
<commit_msg>use INTERNAL_API_URL if available<commit_after>export function truncate(str, length) {
  if (!str || typeof str !== 'string' || str.length <= length) {
    return str;
  }
  const subString = str.substr(0, length-1);
  return `${subString.substr(0, subString.lastIndexOf(' '))} …`;
}

export function trimObject(obj) {
  const res = {};
  Object.keys(obj).forEach(attr => {
    if (typeof obj[attr] !== 'undefined') {
      res[attr] = obj[attr];
    }
  });
  return res;
}

/**
 * Gives the number of days between two dates
 */
export const days = (d1, d2 = new Date) => {
  const oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
  return Math.round(Math.abs((new Date(d1).getTime() - new Date(d2).getTime())/(oneDay)));
}

export function filterCollection(array, cond, inverse) {
  if (!array || !cond) return array;

  const test = (obj, cond, depth = 0) => {
    if (depth > 5) return false;
    if (!obj) return false;
    if (cond instanceof RegExp)
      return Boolean(obj.match(cond));
    if (typeof cond === 'string')
      return obj === cond;

    const nextKey = Object.keys(cond)[0];
    return test(obj[nextKey], cond[nextKey], ++depth);
  }

  return array.filter((r) => inverse ? !test(r, cond) : test(r, cond))
}

export const isValidUrl = (url) => {
  if (typeof url !== 'string') return false;
  return Boolean(url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/));
}

export const isValidEmail = (email) => {
  if (typeof email !== 'string') return false;
  return email.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/);
};

export function getCurrencySymbol(currency) {
 const r = Number(0).toLocaleString(currency, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 0});
 return r.replace(/0$/,'');
}

export function resizeImage(imageUrl, { width, height, query, baseUrl }) {
  if (!imageUrl) return null;
  if (!query && imageUrl.match(/\.svg$/)) return imageUrl; // if we don't need to transform the image, no need to proxy it.
  let queryurl = '';
  if (query) {
    queryurl = encodeURIComponent(query);
  } else {
    if (width) queryurl += `&width=${width}`;
    if (height) queryurl += `&height=${height}`;
  }
  return `${process.env.IMAGES_URL || baseUrl || ''}/proxy/images/?src=${encodeURIComponent(imageUrl)}${queryurl}`;
}

export function imagePreview(src, defaultImage, options = { width: 640 }) {

  if (typeof options.width === 'string') {
    options.width = Number(options.width.replace(/rem/,'')) * 10;
  }
  if (typeof options.height === 'string') {
    options.height = Number(options.height.replace(/rem/,'')) * 10;
  }

  if (src) return resizeImage(src, options);
  return defaultImage;
}

export function prettyUrl(url) {
  if (!url) return '';
  return url.replace(/^https?:\/\/(www\.)?/i,'').replace(/\?.+/, '').replace(/\/$/,'');
}

function getLocaleFromCurrency(currency) {
  let locale;
  switch (currency) {
    case 'USD':
      locale = 'en-US';
      break;
    case 'EUR':
      locale = 'en-EU';
      break;
    default:
      locale = currency;
  }
  return locale;
}

export function getQueryParams() {
  const urlParams = {};
  let match;
  const pl     = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) {
 return decodeURIComponent(s.replace(pl, " "));
},
        query  = window.location.search.substring(1);

  // eslint-disable-next-line no-cond-assign
  while (match = search.exec(query)) {
    urlParams[decode(match[1])] = decode(match[2]);
  }
  return urlParams;
}

// source: https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string
function extractHostname(url) {
  let hostname;
  //find & remove protocol (http, ftp, etc.) and get hostname

  if (url.indexOf("://") > -1) {
      hostname = url.split('/')[2];
  } else {
      hostname = url.split('/')[0];
  }

  //find & remove port number
  hostname = hostname.split(':')[0];
  //find & remove "?"
  hostname = hostname.split('?')[0];

  return hostname;
}

export function getDomain(url = '') {
  let domain = extractHostname(url);
  const splitArr = domain.split('.'), arrLen = splitArr.length;

  //extracting the root domain here
  //if there is a subdomain
  if (arrLen > 2) {
      domain = `${splitArr[arrLen - 2]  }.${  splitArr[arrLen - 1]}`;
      //check to see if it's using a Country Code Top Level Domain (ccTLD) (i.e. ".me.uk")
      if (splitArr[arrLen - 1].length == 2 && splitArr[arrLen - 1].length == 2) {
          //this is using a ccTLD
          domain = `${splitArr[arrLen - 3]  }.${  domain}`;
      }
  }
  return domain;
}

export function formatDate(date, options = { month: 'long', year: 'numeric' }) {
  const d = new Date(date);
  const locale = (typeof window !== 'undefined') ? window.navigator.language : options.locale || 'en-US';
  return d.toLocaleDateString(locale, options);
}

export function formatCurrency(amount, currency = 'USD', options = {}) {
  amount = amount / 100;

  let minimumFractionDigits = 2;
  let maximumFractionDigits = 2;

  if (options.hasOwnProperty('minimumFractionDigits')) {
    minimumFractionDigits = options.minimumFractionDigits
  } else if (options.hasOwnProperty('precision')) {
    minimumFractionDigits = options.precision;
    maximumFractionDigits = options.precision;
  }

  return amount.toLocaleString(getLocaleFromCurrency(currency), {
    style: 'currency',
    currency,
    minimumFractionDigits : minimumFractionDigits,
    maximumFractionDigits : maximumFractionDigits
  })
}

export const singular = (str) => {
  if (!str) return '';
  return str.replace(/ies$/,'y').replace(/s$/,'');
}

export const pluralize = (str, n) => {
  return (n > 1) ? `${str}s` : str;
}

export const translateApiUrl = (url) => {
  const withoutParams = (process.env.INTERNAL_API_URL || process.env.API_URL) + (url.replace('/api/', '/'));
  const hasParams = `${url}`.match(/\?/)
  if (process.env.API_KEY) {
    return `${withoutParams}${hasParams ? '&' : '?'}api_key=${process.env.API_KEY}`;
  } else {
    return withoutParams;
  }
};

export const capitalize = (str) => {
  if (typeof str !== 'string') return '';
  str = str.trim();
  if (str.length === 0) return '';
  return `${str[0].toUpperCase()}${str.substr(1)}`;
}

export const trim = (str, length) => {
  if (!str) return '';

  if (str.length <= length) return str;

  const res = [];
  let res_length = 0;
  const words = str.split(' ');
  let i=0;
  while (res_length < length && i < words.length) {
    const w = words[i++];
    res_length += w.length + 1;
    res.push(w);
  }
  return `${res.join(' ')} …`;
}

export const firstSentence = (str, length) => {
  if (!str) return '';

  str = str.replace(/&/g, '&');

  if (str.length <= length) return str;
  const tokens = str.match(/\.|\?|!/);
  if (tokens) {
    str = str.substr(0, tokens.index + 1);
  }
  str = trim(str, length);
  return str;
}
<|endoftext|>