<commit_before>var fs = require("fs");
var path = require("path");
var webpackDevMiddleware = require("webpack-dev-middleware");
var express = require("express");
var socketio = require("socket.io");
var StreamCache = require("stream-cache");
var http = require("http");
var https = require("https");
var httpProxy = require("http-proxy");
var proxy = new httpProxy.createProxyServer();
var serveIndex = require("serve-index");
var historyApiFallback = require("connect-history-api-fallback");

function Server(compiler, options) {
	// Default options
	if(!options) options = {};

	if (options.lazy && !options.filename) {
		throw new Error("'filename' option must be set in lazy mode.");
	}

	this.hot = options.hot;
	this.headers = options.headers;

	// Listening for events
	var invalidPlugin = function() {
		if(this.io) this.io.sockets.emit("invalid");
	}.bind(this);
	compiler.plugin("compile", invalidPlugin);
	compiler.plugin("invalid", invalidPlugin);
	compiler.plugin("done", function(stats) {
		if(!this.io) return;
		this._sendStats(this.io.sockets, stats.toJson());
		this._stats = stats;
	}.bind(this));

	// Prepare live html page
	var livePage = this.livePage = new StreamCache();
	fs.createReadStream(path.join(__dirname, "..", "client", "live.html")).pipe(livePage);

	// Prepare the live js file
	var liveJs = new StreamCache();
	fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js")).pipe(liveJs);

	// Prepare the inlined js file
	var inlinedJs = new StreamCache();
	fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js")).pipe(inlinedJs);

	// Init express server
	var app = this.app = new express();

	// serve webpack bundle
	app.use(this.middleware = webpackDevMiddleware(compiler, options));

	app.get("/__webpack_dev_server__/live.bundle.js", function(req, res) {
		res.setHeader("Content-Type", "application/javascript");
		liveJs.pipe(res);
	});

	app.get("/webpack-dev-server.js", function(req, res) {
		res.setHeader("Content-Type", "application/javascript");
		inlinedJs.pipe(res);
	});

	app.get("/webpack-dev-server/*", function(req, res) {
		res.setHeader("Content-Type", "text/html");
		this.livePage.pipe(res);
	}.bind(this));

	app.get("/webpack-dev-server", function(req, res) {
		res.setHeader("Content-Type", "text/html");
		res.write('<!DOCTYPE html><meta charset="utf-8"/>');
		var path = this.middleware.getFilenameFromUrl(options.publicPath || "/");
		var fs = this.middleware.fileSystem;
		function writeDirectory(baseUrl, basePath) {
			var content = fs.readdirSync(basePath);
			res.write("
");
			content.forEach(function(item) {
				var p = basePath + "/" + item;
				if(fs.statSync(p).isFile()) {
					res.write('
');
					res.write(item);
					res.write('
');
					if(/\.js$/.test(item)) {
						var htmlItem = item.substr(0, item.length - 3);
						res.write('
');
						res.write(htmlItem);
						res.write(' (magic html for ');
						res.write(item);
						res.write(') (webpack-dev-server)
');
					}
				} else {
					res.write('
');
					res.write(item);
					res.write('
');
					writeDirectory(baseUrl + item + "/", p);
					res.write('
');
				}
			});
			res.write("
");
		}
		writeDirectory(options.publicPath || "/", path);
		res.end('');
	}.bind(this));

	if (options.proxy) {
		var paths = Object.keys(options.proxy);
		paths.forEach(function (path) {
			var proxyOptions;
			if (typeof options.proxy[path] === 'string') {
				proxyOptions = {target: options.proxy[path], ws: true};
			} else {
				proxyOptions = options.proxy[path];
			}
			app.all(path, function (req, res) {
				proxy.web(req, res, proxyOptions, function(err){
					var msg = "cannot proxy to " + proxyOptions.target + " (" + err.message + ")";
					this.io.sockets.emit("proxy-error", [msg]);
					res.statusCode = 502;
					res.end();
				}.bind(this));
			}.bind(this));
		}.bind(this));
	}

	if (options.historyApiFallback) {
		// Fall back to /index.html if nothing else matches.
		app.use(historyApiFallback(typeof options.historyApiFallback === 'object' ? options.historyApiFallback : null));
		// include our middleware to ensure it is able to handle '/index.html' requrst after redirect
		app.use(this.middleware);
	}

	if(options.contentBase !== false) {
		var contentBase = options.contentBase || process.cwd();

		if(typeof contentBase === "object") {
			console.log('Using contentBase as a proxy is deprecated and will be removed in the next major version. Please use the proxy option instead.\n\nTo update remove the contentBase option from webpack.config.js and add this:');
			console.log('proxy: {\n\t"*": <your current contentBase configuration>\n}');
			// Proxy every request to contentBase.target
			app.all("*", function(req, res) {
				proxy.web(req, res, contentBase, function(err) {
					var msg = "cannot proxy to " + contentBase.target + " (" + err.message + ")";
					this.io.sockets.emit("proxy-error", [msg]);
					res.statusCode = 502;
					res.end();
				}.bind(this));
			}.bind(this));
		} else if(/^(https?:)?\/\//.test(contentBase)) {
			// Redirect every request to contentBase
			app.get("*", function(req, res) {
				res.writeHead(302, {
					'Location': contentBase + req.path + (req._parsedUrl.search || "")
				});
				res.end();
			}.bind(this));
		} else if(typeof contentBase === "number") {
			// Redirect every request to the port contentBase
			app.get("*", function(req, res) {
				res.writeHead(302, {
					'Location': "//localhost:" + contentBase + req.path + (req._parsedUrl.search || "")
				});
				res.end();
			}.bind(this));
		} else {
			// route content request
			app.get("*", this.setContentHeaders.bind(this), this.serveMagicHtml.bind(this), express.static(contentBase), serveIndex(contentBase));
		}
	}

	this.listeningApp = options.https
		? https.createServer({
				// using built-in self-signed certificate
				key: options.key || fs.readFileSync(path.join(__dirname, "../ssl/server.key")),
				cert: options.cert || fs.readFileSync(path.join(__dirname, "../ssl/server.crt")),
				ca: options.ca || fs.readFileSync(path.join(__dirname, "../ssl/ca.crt")),
				requestCert: true,
				rejectUnauthorized: false
			}, app)
		: http.createServer(app);
}

Server.prototype.use = function() {
	this.app.use.apply(this.app, arguments);
}

Server.prototype.setContentHeaders = function(req, res, next) {
	if(this.headers) {
		for(var name in this.headers) {
			res.setHeader(name, this.headers[name]);
		}
	}

	next();
}

// delegate listen call and init socket.io
Server.prototype.listen = function() {
	this.listeningApp.listen.apply(this.listeningApp, arguments);
	this.io = socketio.listen(this.listeningApp, {
		"log level": 1
	});
	this.io.sockets.on("connection", function(socket) {
		if(this.hot) socket.emit("hot");
		if(!this._stats) return;
		this._sendStats(socket, this._stats.toJson(), true);
	}.bind(this));
}

Server.prototype.serveMagicHtml = function(req, res, next) {
	var _path = req.path;
	try {
		if(!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(_path + ".js")).isFile())
			return next();
		// Serve a page that executes the javascript
		res.write('<!DOCTYPE html><meta charset="utf-8"/>');
	} catch(e) { return next(); }
}

// send stats to a socket or multiple sockets
Server.prototype._sendStats = function(socket, stats, force) {
	if(!force && stats && stats.assets && stats.assets.every(function(asset) {
		return !asset.emitted;
	})) return socket.emit("still-ok");;
	socket.emit("hash", stats.hash);
	if(stats.errors.length > 0)
		socket.emit("errors", stats.errors);
	else if(stats.warnings.length > 0)
		socket.emit("warnings", stats.warnings);
	else
		socket.emit("ok");
}

Server.prototype.invalidate = function() {
	if(this.middleware) this.middleware.invalidate();
}

module.exports = Server;
<commit_msg>Add host header to proxy request<commit_after>var fs = require("fs");
var path = require("path");
var webpackDevMiddleware = require("webpack-dev-middleware");
var express = require("express");
var socketio = require("socket.io");
var StreamCache = require("stream-cache");
var http = require("http");
var https = require("https");
var httpProxy = require("http-proxy");
var proxy = new httpProxy.createProxyServer();
var serveIndex = require("serve-index");
var historyApiFallback = require("connect-history-api-fallback");

function Server(compiler, options) {
	// Default options
	if(!options) options = {};

	if (options.lazy && !options.filename) {
		throw new Error("'filename' option must be set in lazy mode.");
	}

	this.hot = options.hot;
	this.headers = options.headers;

	// Listening for events
	var invalidPlugin = function() {
		if(this.io) this.io.sockets.emit("invalid");
	}.bind(this);
	compiler.plugin("compile", invalidPlugin);
	compiler.plugin("invalid", invalidPlugin);
	compiler.plugin("done", function(stats) {
		if(!this.io) return;
		this._sendStats(this.io.sockets, stats.toJson());
		this._stats = stats;
	}.bind(this));

	// Prepare live html page
	var livePage = this.livePage = new StreamCache();
	fs.createReadStream(path.join(__dirname, "..", "client", "live.html")).pipe(livePage);

	// Prepare the live js file
	var liveJs = new StreamCache();
	fs.createReadStream(path.join(__dirname, "..", "client", "live.bundle.js")).pipe(liveJs);

	// Prepare the inlined js file
	var inlinedJs = new StreamCache();
	fs.createReadStream(path.join(__dirname, "..", "client", "index.bundle.js")).pipe(inlinedJs);

	// Init express server
	var app = this.app = new express();

	// serve webpack bundle
	app.use(this.middleware = webpackDevMiddleware(compiler, options));

	app.get("/__webpack_dev_server__/live.bundle.js", function(req, res) {
		res.setHeader("Content-Type", "application/javascript");
		liveJs.pipe(res);
	});

	app.get("/webpack-dev-server.js", function(req, res) {
		res.setHeader("Content-Type", "application/javascript");
		inlinedJs.pipe(res);
	});

	app.get("/webpack-dev-server/*", function(req, res) {
		res.setHeader("Content-Type", "text/html");
		this.livePage.pipe(res);
	}.bind(this));

	app.get("/webpack-dev-server", function(req, res) {
		res.setHeader("Content-Type", "text/html");
		res.write('<!DOCTYPE html><meta charset="utf-8"/>');
		var path = this.middleware.getFilenameFromUrl(options.publicPath || "/");
		var fs = this.middleware.fileSystem;
		function writeDirectory(baseUrl, basePath) {
			var content = fs.readdirSync(basePath);
			res.write("
");
			content.forEach(function(item) {
				var p = basePath + "/" + item;
				if(fs.statSync(p).isFile()) {
					res.write('
');
					res.write(item);
					res.write('
');
					if(/\.js$/.test(item)) {
						var htmlItem = item.substr(0, item.length - 3);
						res.write('
');
						res.write(htmlItem);
						res.write(' (magic html for ');
						res.write(item);
						res.write(') (webpack-dev-server)
');
					}
				} else {
					res.write('
');
					res.write(item);
					res.write('
');
					writeDirectory(baseUrl + item + "/", p);
					res.write('
');
				}
			});
			res.write("
");
		}
		writeDirectory(options.publicPath || "/", path);
		res.end('');
	}.bind(this));

	if (options.proxy) {
		var paths = Object.keys(options.proxy);
		paths.forEach(function (path) {
			var proxyOptions;
			if (typeof options.proxy[path] === 'string') {
				proxyOptions = {target: options.proxy[path], ws: true};
			} else {
				proxyOptions = options.proxy[path];
			}
			app.all(path, function (req, res) {
				if (proxyOptions.host) {
					req.headers.host = proxyOptions.host;
				}
				proxy.web(req, res, proxyOptions, function(err){
					var msg = "cannot proxy to " + proxyOptions.target + " (" + err.message + ")";
					this.io.sockets.emit("proxy-error", [msg]);
					res.statusCode = 502;
					res.end();
				}.bind(this));
			}.bind(this));
		}.bind(this));
	}

	if (options.historyApiFallback) {
		// Fall back to /index.html if nothing else matches.
		app.use(historyApiFallback(typeof options.historyApiFallback === 'object' ? options.historyApiFallback : null));
		// include our middleware to ensure it is able to handle '/index.html' requrst after redirect
		app.use(this.middleware);
	}

	if(options.contentBase !== false) {
		var contentBase = options.contentBase || process.cwd();

		if(typeof contentBase === "object") {
			console.log('Using contentBase as a proxy is deprecated and will be removed in the next major version. Please use the proxy option instead.\n\nTo update remove the contentBase option from webpack.config.js and add this:');
			console.log('proxy: {\n\t"*": <your current contentBase configuration>\n}');
			// Proxy every request to contentBase.target
			app.all("*", function(req, res) {
				proxy.web(req, res, contentBase, function(err) {
					var msg = "cannot proxy to " + contentBase.target + " (" + err.message + ")";
					this.io.sockets.emit("proxy-error", [msg]);
					res.statusCode = 502;
					res.end();
				}.bind(this));
			}.bind(this));
		} else if(/^(https?:)?\/\//.test(contentBase)) {
			// Redirect every request to contentBase
			app.get("*", function(req, res) {
				res.writeHead(302, {
					'Location': contentBase + req.path + (req._parsedUrl.search || "")
				});
				res.end();
			}.bind(this));
		} else if(typeof contentBase === "number") {
			// Redirect every request to the port contentBase
			app.get("*", function(req, res) {
				res.writeHead(302, {
					'Location': "//localhost:" + contentBase + req.path + (req._parsedUrl.search || "")
				});
				res.end();
			}.bind(this));
		} else {
			// route content request
			app.get("*", this.setContentHeaders.bind(this), this.serveMagicHtml.bind(this), express.static(contentBase), serveIndex(contentBase));
		}
	}

	this.listeningApp = options.https
		? https.createServer({
				// using built-in self-signed certificate
				key: options.key || fs.readFileSync(path.join(__dirname, "../ssl/server.key")),
				cert: options.cert || fs.readFileSync(path.join(__dirname, "../ssl/server.crt")),
				ca: options.ca || fs.readFileSync(path.join(__dirname, "../ssl/ca.crt")),
				requestCert: true,
				rejectUnauthorized: false
			}, app)
		: http.createServer(app);
}

Server.prototype.use = function() {
	this.app.use.apply(this.app, arguments);
}

Server.prototype.setContentHeaders = function(req, res, next) {
	if(this.headers) {
		for(var name in this.headers) {
			res.setHeader(name, this.headers[name]);
		}
	}

	next();
}

// delegate listen call and init socket.io
Server.prototype.listen = function() {
	this.listeningApp.listen.apply(this.listeningApp, arguments);
	this.io = socketio.listen(this.listeningApp, {
		"log level": 1
	});
	this.io.sockets.on("connection", function(socket) {
		if(this.hot) socket.emit("hot");
		if(!this._stats) return;
		this._sendStats(socket, this._stats.toJson(), true);
	}.bind(this));
}

Server.prototype.serveMagicHtml = function(req, res, next) {
	var _path = req.path;
	try {
		if(!this.middleware.fileSystem.statSync(this.middleware.getFilenameFromUrl(_path + ".js")).isFile())
			return next();
		// Serve a page that executes the javascript
		res.write('<!DOCTYPE html><meta charset="utf-8"/>');
	} catch(e) { return next(); }
}

// send stats to a socket or multiple sockets
Server.prototype._sendStats = function(socket, stats, force) {
	if(!force && stats && stats.assets && stats.assets.every(function(asset) {
		return !asset.emitted;
	})) return socket.emit("still-ok");;
	socket.emit("hash", stats.hash);
	if(stats.errors.length > 0)
		socket.emit("errors", stats.errors);
	else if(stats.warnings.length > 0)
		socket.emit("warnings", stats.warnings);
	else
		socket.emit("ok");
}

Server.prototype.invalidate = function() {
	if(this.middleware) this.middleware.invalidate();
}

module.exports = Server;
<|endoftext|>


<commit_before>const BaseChecker = require('./../base-checker')

const ruleId = 'comprehensive-interface'
const meta = {
  type: 'miscellaneous',

  docs: {
    description:
      'Check that all public or external functions are override. This is iseful to make sure that the whole API is extracted in an interface.',
    category: 'Miscellaneous',
    examples: {
      good: require('../../../test/fixtures/miscellaneous/public-function-with-override'),
      bad: require('../../../test/fixtures/miscellaneous/public-function-no-override')
    }
  },

  isDefault: false,
  recommended: false,
  defaultSetup: 'warn',

  schema: null
}

class ComprehensiveInterface extends BaseChecker {
  constructor(reporter) {
    super(reporter, ruleId, meta)
  }

  ContractDefinition(node) {
    this.isContract = node.kind === 'contract'
  }

  'ContractDefinition:exit'() {
    this.isContract = false
  }

  FunctionDefinition(node) {
    if (!this.isContract) {
      return
    }

    const isPublic = node.visibility === 'public' || node.visibility === 'external'
    const isOverride = node.override !== null

    if (isPublic && !isOverride) {
      this.error(node, 'All public methods have to be an override')
    }
  }
}

module.exports = ComprehensiveInterface
<commit_msg>Update lib/rules/miscellaneous/comprehensive-interface.js<commit_after>const BaseChecker = require('./../base-checker')

const ruleId = 'comprehensive-interface'
const meta = {
  type: 'miscellaneous',

  docs: {
    description:
      'Check that all public or external functions are override. This is iseful to make sure that the whole API is extracted in an interface.',
    category: 'Miscellaneous',
    examples: {
      good: require('../../../test/fixtures/miscellaneous/public-function-with-override'),
      bad: require('../../../test/fixtures/miscellaneous/public-function-no-override')
    }
  },

  isDefault: false,
  recommended: false,
  defaultSetup: 'warn',

  schema: null
}

class ComprehensiveInterface extends BaseChecker {
  constructor(reporter) {
    super(reporter, ruleId, meta)
  }

  ContractDefinition(node) {
    this.isContract = node.kind === 'contract'
  }

  'ContractDefinition:exit'() {
    this.isContract = false
  }

  FunctionDefinition(node) {
    if (!this.isContract) {
      return
    }

    const isPublic = node.visibility === 'public' || node.visibility === 'external'
    const isOverride = node.override !== null

    if (isPublic && !isOverride) {
      this.error(node, 'All public or external methods in a contract must override a definition from an interface')
    }
  }
}

module.exports = ComprehensiveInterface
<|endoftext|>


<commit_before>(function (module, process) {
    'use strict';
    var colors = require('colors'),
        request = require('./request/main'),
        _ = require('underscore'),
        Q = require('q');

    colors.setTheme({
        silly:'rainbow',
        input:'grey',
        verbose:'cyan',
        prompt:'grey',
        info:'green',
        data:'grey',
        help:'cyan',
        warn:'yellow',
        debug:'blue',
        error:'red'
    });

    var translateOptions2Arguments = function (options) {
        //used to translate property to cmd line arg
        var argMap = {
            'ignoreSslErrors':'ignore-ssl-errors',
            'localToRemoteUrlAccessEnabled':'local-to-remote-url-access',
            'cookiesFile':'cookies-file',
            'diskCache':'disk-cache',
            'loadImages':'load-images',
            'maxDiskCache':'max-disk-cache',
            'outputEncoding':'output-encoding',
            'proxy':'proxy',
            'proxyType':'proxy-type',
            'scriptEncoding':'script-encoding',
            'version':'version',
            'webSecurity':'web-security',
            'port':'port'
        };

        //may integrate in future
        var defaultoptions = {
            'ignoreSslErrors':true,
            'localToRemoteUrlAccessEnabled':true,
            'cookiesFile':'cookies.txt',
            'diskCache':'yes',
            'loadImages':'yes',
            'localToRemoteUrlAccess':'no',
            'maxDiskCache':'50000',
            'outputEncoding':'utf8',
            'proxy':'0',
            'proxyType':'yes',
            'scriptEncoding':'yes',
            'webSecurity':'yes',
            'port':1061
        };

        //map options props to cmd line args for phantom process
        return Object.keys(options).map(function (value) {
            return '--' + argMap[value] + '=' + options[value];
        });

    };

    //on exit loop through process array and kill everything
    process.on('exit', function () {
        processArray.forEach(function (proc) {
            proc.kill('SIGHUP');
        });
    });

    var processArray =
            [
            ], //holds references to any spawned phantomjs procs
        phantomProcess, //current phantomjs process

    //creates a new phantomjs process
        createServer = function (options, callbackFn) {
            var fs = require('fs'),
                self = this,
                es = require('event-stream'),
                streamPosition = 0,

            //parses a line from phantoms event stream
                parseDataBuffer = function (dataBuffer) {
                    var self = this;

                    this.options.debug && console.log('buffer :%s', dataBuffer.grey);

                    try {
                        var parsedEvent = JSON.parse(dataBuffer);
                    }
                    catch (exception) {
                        console.log(exception.toString().red.bold);
                        return;
                    }

                    switch (parsedEvent.source) {
                        //phantomJs server module started and is listening or failed
                        case 'server' :

                            //check for server module failure
                            if (!parsedEvent.args[0].result) {
                                throw new Error('unable to start phantomjs server module');
                            }

                            options.debug && console.log('server created'.green);

                            //build proxy and execute callback
                            var Webpage = require('./webpage'),
                                page = page || new Webpage(options, phantomProcess),
                                phantom = require('./phantom').create(options, phantomProcess);

                            self.proxy = {
                                page:page,
                                phantom:phantom,
                                end:function (endCallback) {
                                    self.end(function () {
                                        endCallback(true);
                                    });
                                }
                            };

                            //pass proxy to callbackFn
                            callbackFn.call(self, self.proxy);
                            break;
                        default :
                            this.options.debug && console.log('emmiting %s event'.grey, parsedEvent.source);

                            //pass args as array w/ out source argument, then emit event on page object
                            var args = parsedEvent.args;
                            args.unshift(parsedEvent.source);
                            self.proxy.page.emit.apply(self.proxy.page, parsedEvent);
                    }

                };

            fs.open(options.eventStreamPath, 'w+', function (err, fd) {
                self.options.debug && console.log('opening'.green, options.eventStreamPath);
                fs.watchFile(self.options.eventStreamPath, {interval:100}, onFileChanged);
            });

            var createReadStream = function () {

                self.options.debug && console.log('reading %s'.green, options.eventStreamPath);

                var streamReader = fs.createReadStream(options.eventStreamPath, {start:streamPosition });

                streamReader.on('end', function () {
                    self.options.debug && console.log('EOF'.yellow);
                    streamReader.destroy();
                });

                return streamReader;
            };

            var onFileChanged = function (curr, prev) {
                options.debug && console.log('file changed');

                es.pipeline(
                    createReadStream(),
                    es.split(),
                    es.map(function (line, cb) {
                        streamPosition += line.length + 1;
                        parseDataBuffer.call(self, line);

                        options.debug && console.log('parsed %d bytes so far', streamPosition);
                        cb(null, line)
                    })
                );
            };

            var spawn = require('child_process').spawn,
                serverPath = __dirname + '/' + require('path').normalize('./server/webserver.js');

            var processArgs = translateOptions2Arguments(options);

            processArgs.unshift(JSON.stringify(options));
            processArgs.unshift(serverPath);

            //spawn new process
            phantomProcess =
                spawn('phantomjs',
                    processArgs,
                    {
                        detatched:true,
                        stdio:
                            [
                                process.stdin,
                                process.stdout,
                                process.stderr
                            ]
                    });

            phantomProcess.on('exit', function (code, signal) {
                self.options.debug && console.log('phantom exited, unwatching event file');
                fs.unwatchFile(self.options.eventStreamPath, onFileChanged);
            });

            phantomProcess.on('close', function () {
                console.log('close called'.green);
            });

            phantomProcess.on('disconnect', function () {
                console.log('disconnect called'.green);
            });
            phantomProcess.on('message', function () {
                console.log('message called'.green);
            });

            processArray.push(phantomProcess);
        },
//use to see if  phantom server already runnings
        pingServer = function (options, callbackFn) {

            var self = this;
            request.get(options.hostAndPort + '/ping',
                function (error, response) {

                    if (response && response.statusCode === 200) {    //server up

                        callbackFn && callbackFn.call(self, true);
                    }
                    else {    //server down
                        callbackFn.call(self, false);
                    }
                });
            return this;
        };

    module.exports = {
        //terminates phantomjs process
        end:function (callbackFn) {
            var self = this;
            phantomProcess.on('exit', function () {
                self.options.debug && console.log('phantom process exited'.green);
                callbackFn(true);
            });
            request.post(this.options.hostAndPort + '/phantom/functions/exit', {
                    form:{
                    }
                },
                function (error, response, body) {
                    self.options.debug && console.log('exit request sent. response was : %s', body);
                });
        },
        //creates a new proxy session - creates phantomjs process and webserver module
        create:function (options, callbackFn) {
            var self = this;

            //compensate for optional options parm
            if (typeof options === 'function') {
                callbackFn = options;
                options = {};
            }

            //assign default port
            options.port = options.port || 1061;
            options.host = options.host || 'localhost';
            options.hostAndPort = 'http://' + options.host + ':' + options.port;
            options.eventStreamPath = require('path').normalize(__dirname + '/../temp/events.txt');

            options.debug && console.log('creating proxy to %s', options.hostAndPort);

            this.options = options;

            //try starting server
            createServer.call(self, options, function (proxy) {
                callbackFn.call(self, proxy);
            });

            return this;//return this to chain end
        }};
}(module, process));

<commit_msg>reworked events - version 0.1.66<commit_after>(function (module, process) {
    'use strict';
    var colors = require('colors'),
        request = require('./request/main'),
        _ = require('underscore'),
        Q = require('q'),
        events = require('events');

    colors.setTheme({
        silly:'rainbow',
        input:'grey',
        verbose:'cyan',
        prompt:'grey',
        info:'green',
        data:'grey',
        help:'cyan',
        warn:'yellow',
        debug:'blue',
        error:'red'
    });

    var translateOptions2Arguments = function (options) {
        //used to translate property to cmd line arg
        var argMap = {
            'ignoreSslErrors':'ignore-ssl-errors',
            'localToRemoteUrlAccessEnabled':'local-to-remote-url-access',
            'cookiesFile':'cookies-file',
            'diskCache':'disk-cache',
            'loadImages':'load-images',
            'maxDiskCache':'max-disk-cache',
            'outputEncoding':'output-encoding',
            'proxy':'proxy',
            'proxyType':'proxy-type',
            'scriptEncoding':'script-encoding',
            'version':'version',
            'webSecurity':'web-security',
            'port':'port'
        };

        //may integrate in future
        var defaultoptions = {
            'ignoreSslErrors':true,
            'localToRemoteUrlAccessEnabled':true,
            'cookiesFile':'cookies.txt',
            'diskCache':'yes',
            'loadImages':'yes',
            'localToRemoteUrlAccess':'no',
            'maxDiskCache':'50000',
            'outputEncoding':'utf8',
            'proxy':'0',
            'proxyType':'yes',
            'scriptEncoding':'yes',
            'webSecurity':'yes',
            'port':1061
        };

        //map options props to cmd line args for phantom process
        return Object.keys(options).map(function (value) {
            return '--' + argMap[value] + '=' + options[value];
        });

    };

    //on exit loop through process array and kill everything
    process.on('exit', function () {
        processArray.forEach(function (proc) {
            proc.kill('SIGHUP');
        });
    });

    var processArray =
            [
            ], //holds references to any spawned phantomjs procs
        phantomProcess, //current phantomjs process

    //creates a new phantomjs process
        createServer = function (options, callbackFn) {
            var fs = require('fs'),
                self = this,
                es = require('event-stream'),
                streamPosition = 0,

            //parses a line from phantoms event stream
                parseDataBuffer = function (dataBuffer) {
                    var self = this;

                    this.options.debug && console.log('buffer :%s', dataBuffer.grey);

                    try {
                        var parsedEvent = JSON.parse(dataBuffer);
                    }
                    catch (exception) {
                        console.log(exception.toString().red.bold);
                        return;
                    }

                    switch (parsedEvent.source) {
                        //phantomJs server module started and is listening or failed
                        case 'server' :

                            //check for server module failure
                            if (!parsedEvent.args[0].result) {
                                throw new Error('unable to start phantomjs server module');
                            }

                            options.debug && console.log('server created'.green);

                            //build proxy and execute callback
                            var Webpage = require('./webpage'),
                                page = page || new Webpage(options, phantomProcess),
                                phantom = require('./phantom').create(options, phantomProcess);

                            self.proxy = {
                                page:page,
                                phantom:phantom,
                                end:function (endCallback) {
                                    self.end(function () {
                                        endCallback(true);
                                    });
                                }
                            };

                            //pass proxy to callbackFn
                            callbackFn.call(self, self.proxy);
                            break;
                        default :
                            this.options.debug && console.log('emmiting %s event'.grey, parsedEvent.source);

                            //pass args as array w/ out source argument, then emit event on page object
                            var args = parsedEvent.args;
                            args.unshift(parsedEvent.source);
                            self.proxy.page.emit.apply(self.proxy.page, args);
                    }

                };

            fs.open(options.eventStreamPath, 'w+', function (err, fd) {
                self.options.debug && console.log('opening'.green, options.eventStreamPath);
                fs.watchFile(self.options.eventStreamPath, {interval:100}, onFileChanged);
            });

            var createReadStream = function () {

                self.options.debug && console.log('reading %s'.green, options.eventStreamPath);

                var streamReader = fs.createReadStream(options.eventStreamPath, {start:streamPosition });

                streamReader.on('end', function () {
                    self.options.debug && console.log('EOF'.yellow);
                    streamReader.destroy();
                });

                return streamReader;
            };

            var onFileChanged = function (curr, prev) {
                options.debug && console.log('file changed');

                es.pipeline(
                    createReadStream(),
                    es.split(),
                    es.map(function (line, cb) {
                        streamPosition += line.length + 1;
                        parseDataBuffer.call(self, line);

                        options.debug && console.log('parsed %d bytes so far', streamPosition);
                        cb(null, line)
                    })
                );
            };

            var spawn = require('child_process').spawn,
                serverPath = __dirname + '/' + require('path').normalize('./server/webserver.js');

            var processArgs = translateOptions2Arguments(options);

            processArgs.unshift(JSON.stringify(options));
            processArgs.unshift(serverPath);

            //spawn new process
            phantomProcess =
                spawn('phantomjs',
                    processArgs,
                    {
                        detatched:true,
                        stdio:
                            [
                                process.stdin,
                                process.stdout,
                                process.stderr
                            ]
                    });

            phantomProcess.on('exit', function (code, signal) {
                self.options.debug && console.log('phantom exited, unwatching event file');
                fs.unwatchFile(self.options.eventStreamPath, onFileChanged);
            });

            phantomProcess.on('close', function () {
                console.log('close called'.green);
            });

            phantomProcess.on('disconnect', function () {
                console.log('disconnect called'.green);
            });
            phantomProcess.on('message', function () {
                console.log('message called'.green);
            });

            processArray.push(phantomProcess);
        },
//use to see if  phantom server already runnings
        pingServer = function (options, callbackFn) {

            var self = this;
            request.get(options.hostAndPort + '/ping',
                function (error, response) {

                    if (response && response.statusCode === 200) {    //server up

                        callbackFn && callbackFn.call(self, true);
                    }
                    else {    //server down
                        callbackFn.call(self, false);
                    }
                });
            return this;
        };

    module.exports = {
        //terminates phantomjs process
        end:function (callbackFn) {
            var self = this;
            phantomProcess.on('exit', function () {
                self.options.debug && console.log('phantom process exited'.green);
                callbackFn(true);
            });
            request.post(this.options.hostAndPort + '/phantom/functions/exit', {
                    form:{
                    }
                },
                function (error, response, body) {
                    self.options.debug && console.log('exit request sent. response was : %s', body);
                });
        },
        //creates a new proxy session - creates phantomjs process and webserver module
        create:function (options, callbackFn) {
            var self = this;

            //compensate for optional options parm
            if (typeof options === 'function') {
                callbackFn = options;
                options = {};
            }

            //assign default port
            options.port = options.port || 1061;
            options.host = options.host || 'localhost';
            options.hostAndPort = 'http://' + options.host + ':' + options.port;
            options.eventStreamPath = require('path').normalize(__dirname + '/../temp/events.txt');

            options.debug && console.log('creating proxy to %s', options.hostAndPort);

            this.options = options;

            //try starting server
            createServer.call(self, options, function (proxy) {
                callbackFn.call(self, proxy);
            });

            return this;//return this to chain end
        }};
}(module, process));

<|endoftext|>


<commit_before>var mmm = require("multi-master-merge");
var level = require("level"); // LevelUP + LevelDOWN
var path = require("path");
var url = require("url");
var mkdirp = require("mkdirp");
var sublevel = require("level-sublevel");
var Map = require("es6-map");
var _ = require("lodash");
var async = require("async");
var net = require("net");
var DHT = require("bittorrent-dht");
var SSDP = require("node-ssdp");
var ip = require("ip");
var zlib = require("zlib");
var duplexify = require("duplexify");
var events = require("events");

// hole punch for our replication interface
var entry = require("entry");
var natPmp = require("nat-pmp");

var log = require("./log");
var cfg = require("./cfg");

var dht = new DHT();
var ssdp = new SSDP.Client();

mkdirp.sync(cfg.dbPath);
var db = mmm(level(cfg.dbPath), { encoding: "json", gc: true, postupdate: updateIndexes });
var AVLTree = require("binary-search-tree").AVLTree;
var mmmEnc = require("multi-master-merge/encoders")("json");

/* Indexes
 */
db.evs = new events.EventEmitter();
db.indexes = { meta: new AVLTree(), seeders: new Map(), updated: new Map() };
function getHashes(x) {
	return (Array.isArray(x.episode) ? x.episode : [x.episode]).map(function(ep) { // separate hashes for multi-episode files
		return [x.imdb_id, x.season, ep ].filter(function(x) { return x }).join(" ")
	});
}
function updateIndexes(entry, callback)
{
	var torrent = entry.value;
	if (! torrent) return callback && callback();
	var maxSeeders = db.getMaxPopularity(torrent);

	db.evs.emit("idxbuild", torrent, entry.peer, entry.seq);

	db.indexes.seeders.set(torrent.infoHash, maxSeeders);
	db.indexes.updated.set(torrent.infoHash, torrent.updated);
	if (torrent.files) torrent.files.forEach(function(x) {
		if (db.isFileBlacklisted(x)) return;
		getHashes(x).forEach(function(hash) {
			db.indexes.meta.delete(hash, torrent.infoHash); // always ensure there are no duplicates
			//if (maxSeeders > cfg.minSeedToIndex) db.indexes.meta.insert(hash, torrent.infoHash);
			db.indexes.meta.insert(hash, torrent.infoHash); // currently index everything
			// WARNING: if we use the maxSeeders check, we risk to run this codepath with a conflicted copy of the torrent which doesn't have seeds
			// for now this is un-solvable
		});
	});
	callback && callback();
}

db.createLogStream().on("data", function(entry) {
	updateIndexes({ peer: entry.peer, seq: entry.seq, value: entry.value });
}).on("end", function() { db.evs.emit("idxready") });

/* Compact with old events API
 * this should maybe be obsoleted?
 */
Object.defineProperty(db, "onIdxBuild", { set: function(fn) { db.evs.on("idxbuild", fn) } });
Object.defineProperty(db, "onIdxReady", { set: function(fn) { db.evs.on("idxready", fn) } });

/* Utilities
 */
db.getMaxPopularity = function(torrent) {
	return Math.max.apply(Math, _.values(torrent.popularity).map(function(x) { return x[0] }).concat(0));
};
db.isFileBlacklisted = function(file) {
	return file.tag.concat(require("./utils").tags(file)).some(function(tag) { return cfg.blacklisted[tag] });
};
db.getSourcesForTorrent = function(torrent) {
	return undefined; // Stremio Defaults
};
db.getAvailForTorrent = function(torrent) {
    var maxSeeders = db.getMaxPopularity(torrent);
    if (maxSeeders >= 300) return 4;
    if (maxSeeders >= 90) return 3;
    if (maxSeeders >= 15) return 2;
    if (maxSeeders > 0) return 1;
    return 0;
};

/* Querying
 */
db.lookup = function(query, items)
{
	return db.indexes.meta.search(getHashes(query)[0])
		.map(function(id) { return { id: id, seeders: db.indexes.seeders.get(id) || 0 } })
		.sort(function(a,b) { return b.seeders - a.seeders })
		.slice(0, items);
};

// query - { imdb_id, season?, episode? } ; items - number of items to return, callback - function(err, items)
db.find = function(query, items, callback)
{
	async.map(db.lookup(query, items), function(x, cb) {
		return db.get(x.id, function(err, res) { cb(err, res && res[0] && res[0].value) });
	}, callback);
};

/* Replication - server & swarm
 */
db.syncStream = function()
{
	var stream = db.sync();
	// return stream;
	return stream.pipe(duplexify(zlib.createGzip(), zlib.createGunzip())).pipe(stream);
};

var server;
db.listenReplications = function(id) {
	server = net.createServer(function(c) {
		log.important("DB replication connection established from "+c.remoteAddress+":"+c.remotePort);
		c.pipe(db.syncStream()).pipe(c);
		c.on("error", function(err) { console.log(err) });
	});
	server.listen(function() {
		var port = server.address().port;

		log.important("DB replication server listening at "+port);
		dht.announce(id, port);

		// Hole punch so this server is accessible behind router firewalls
		// We can play around with public / private - maybe we want a consistent public port?
		try {
			// Both throw err in their async code which we cannot catch
			entry.map({ external: port, internal: port, name: require("../package").name }, function(e) { e && console.error("entry error (non-fatal)", e) });
			//natPmp.connect("10.0.1.1").portMapping({ public: port, private: port, ttl: 1000, description: require("../package").name }, function(e) { e && console.error(e) });
		} catch(e) { console.error("entry error (non-fatal)", e) }
	});

	// Announce as an SSDP server
	var ssdpServer = new SSDP.Server({ location: ip.address() +":" + server.address().port });
	ssdpServer.addUSN("upnp:rootdevice");
	ssdpServer.addUSN("urn:schemas-upnp-org:service:MultiPassTorrent:"+id);
	ssdpServer.start("0.0.0.0");
};

db.findReplications = function(id) {
	log.important(id+": finding other instances to replicate with through "+[cfg.ssdp ? "ssdp" : "", cfg.dht ? "dht" : ""].join(", "));

	// WARNING: what if it emits beforehand?
	if (cfg.dht) dht.on("ready", function() {
		dht.lookup(id);
	});

	if (cfg.ssdp) ssdp.search("urn:schemas-upnp-org:service:MultiPassTorrent:"+id);
};

/* Swarm
 */
var peers = { };
function onpeer(addr)
{
	if (ip.address()+":"+server.address().port == addr) return; // Do not connect to ourselves

	if (peers[addr]) return;
	peers[addr] = true;

	var spl = addr.split(":");
	var c = net.connect(spl[1], spl[0]);
	c.on("connect", function() {
		log.message("connected to peer "+addr); // TODO: handle errs
		c.pipe(db.syncStream()).pipe(c);
	}).on("error", function(e) {
		c.destroy()
	}).on("end", function() {
		c.destroy();
		delete peers[addr];
	}).on("close", function() {
		// cleanup sync pipe?
	});
};
dht.on("peer", onpeer);
ssdp.on("response", function(meta, status, matchine) { onpeer(meta.LOCATION) });

module.exports = db;
<commit_msg>put torrent in meta index only if it has some popularity<commit_after>var mmm = require("multi-master-merge");
var level = require("level"); // LevelUP + LevelDOWN
var path = require("path");
var url = require("url");
var mkdirp = require("mkdirp");
var sublevel = require("level-sublevel");
var Map = require("es6-map");
var _ = require("lodash");
var async = require("async");
var net = require("net");
var DHT = require("bittorrent-dht");
var SSDP = require("node-ssdp");
var ip = require("ip");
var zlib = require("zlib");
var duplexify = require("duplexify");
var events = require("events");

// hole punch for our replication interface
var entry = require("entry");
var natPmp = require("nat-pmp");

var log = require("./log");
var cfg = require("./cfg");

var dht = new DHT();
var ssdp = new SSDP.Client();

mkdirp.sync(cfg.dbPath);
var db = mmm(level(cfg.dbPath), { encoding: "json", gc: true, postupdate: updateIndexes });
var AVLTree = require("binary-search-tree").AVLTree;
var mmmEnc = require("multi-master-merge/encoders")("json");

/* Indexes
 */
db.evs = new events.EventEmitter();
db.indexes = { meta: new AVLTree(), seeders: new Map(), updated: new Map() };
function getHashes(x) {
	return (Array.isArray(x.episode) ? x.episode : [x.episode]).map(function(ep) { // separate hashes for multi-episode files
		return [x.imdb_id, x.season, ep ].filter(function(x) { return x }).join(" ")
	});
}
function updateIndexes(entry, callback)
{
	var torrent = entry.value;
	if (! torrent) return callback && callback();
	var maxSeeders = db.getMaxPopularity(torrent);

	db.evs.emit("idxbuild", torrent, entry.peer, entry.seq);

	db.indexes.seeders.set(torrent.infoHash, maxSeeders);
	db.indexes.updated.set(torrent.infoHash, torrent.updated);
	if (torrent.files && maxSeeders) torrent.files.forEach(function(x) {
		if (db.isFileBlacklisted(x)) return;
		getHashes(x).forEach(function(hash) {
			db.indexes.meta.delete(hash, torrent.infoHash); // always ensure there are no duplicates
			//if (maxSeeders > cfg.minSeedToIndex) db.indexes.meta.insert(hash, torrent.infoHash);
			db.indexes.meta.insert(hash, torrent.infoHash); // currently index everything
			// WARNING: if we use the maxSeeders check, we risk to run this codepath with a conflicted copy of the torrent which doesn't have seeds
			// for now this is un-solvable
		});
	});
	callback && callback();
}

db.createLogStream().on("data", function(entry) {
	updateIndexes({ peer: entry.peer, seq: entry.seq, value: entry.value });
}).on("end", function() { db.evs.emit("idxready") });

/* Compact with old events API
 * this should maybe be obsoleted?
 */
Object.defineProperty(db, "onIdxBuild", { set: function(fn) { db.evs.on("idxbuild", fn) } });
Object.defineProperty(db, "onIdxReady", { set: function(fn) { db.evs.on("idxready", fn) } });

/* Utilities
 */
db.getMaxPopularity = function(torrent) {
	return Math.max.apply(Math, _.values(torrent.popularity).map(function(x) { return x[0] }).concat(0));
};
db.isFileBlacklisted = function(file) {
	return file.tag.concat(require("./utils").tags(file)).some(function(tag) { return cfg.blacklisted[tag] });
};
db.getSourcesForTorrent = function(torrent) {
	return undefined; // Stremio Defaults
};
db.getAvailForTorrent = function(torrent) {
    var maxSeeders = db.getMaxPopularity(torrent);
    if (maxSeeders >= 300) return 4;
    if (maxSeeders >= 90) return 3;
    if (maxSeeders >= 15) return 2;
    if (maxSeeders > 0) return 1;
    return 0;
};

/* Querying
 */
db.lookup = function(query, items)
{
	return db.indexes.meta.search(getHashes(query)[0])
		.map(function(id) { return { id: id, seeders: db.indexes.seeders.get(id) || 0 } })
		.sort(function(a,b) { return b.seeders - a.seeders })
		.slice(0, items);
};

// query - { imdb_id, season?, episode? } ; items - number of items to return, callback - function(err, items)
db.find = function(query, items, callback)
{
	async.map(db.lookup(query, items), function(x, cb) {
		return db.get(x.id, function(err, res) { cb(err, res && res[0] && res[0].value) });
	}, callback);
};

/* Replication - server & swarm
 */
db.syncStream = function()
{
	var stream = db.sync();
	// return stream;
	return stream.pipe(duplexify(zlib.createGzip(), zlib.createGunzip())).pipe(stream);
};

var server;
db.listenReplications = function(id) {
	server = net.createServer(function(c) {
		log.important("DB replication connection established from "+c.remoteAddress+":"+c.remotePort);
		c.pipe(db.syncStream()).pipe(c);
		c.on("error", function(err) { console.log(err) });
	});
	server.listen(function() {
		var port = server.address().port;

		log.important("DB replication server listening at "+port);
		dht.announce(id, port);

		// Hole punch so this server is accessible behind router firewalls
		// We can play around with public / private - maybe we want a consistent public port?
		try {
			// Both throw err in their async code which we cannot catch
			entry.map({ external: port, internal: port, name: require("../package").name }, function(e) { e && console.error("entry error (non-fatal)", e) });
			//natPmp.connect("10.0.1.1").portMapping({ public: port, private: port, ttl: 1000, description: require("../package").name }, function(e) { e && console.error(e) });
		} catch(e) { console.error("entry error (non-fatal)", e) }
	});

	// Announce as an SSDP server
	var ssdpServer = new SSDP.Server({ location: ip.address() +":" + server.address().port });
	ssdpServer.addUSN("upnp:rootdevice");
	ssdpServer.addUSN("urn:schemas-upnp-org:service:MultiPassTorrent:"+id);
	ssdpServer.start("0.0.0.0");
};

db.findReplications = function(id) {
	log.important(id+": finding other instances to replicate with through "+[cfg.ssdp ? "ssdp" : "", cfg.dht ? "dht" : ""].join(", "));

	// WARNING: what if it emits beforehand?
	if (cfg.dht) dht.on("ready", function() {
		dht.lookup(id);
	});

	if (cfg.ssdp) ssdp.search("urn:schemas-upnp-org:service:MultiPassTorrent:"+id);
};

/* Swarm
 */
var peers = { };
function onpeer(addr)
{
	if (ip.address()+":"+server.address().port == addr) return; // Do not connect to ourselves

	if (peers[addr]) return;
	peers[addr] = true;

	var spl = addr.split(":");
	var c = net.connect(spl[1], spl[0]);
	c.on("connect", function() {
		log.message("connected to peer "+addr); // TODO: handle errs
		c.pipe(db.syncStream()).pipe(c);
	}).on("error", function(e) {
		c.destroy()
	}).on("end", function() {
		c.destroy();
		delete peers[addr];
	}).on("close", function() {
		// cleanup sync pipe?
	});
};
dht.on("peer", onpeer);
ssdp.on("response", function(meta, status, matchine) { onpeer(meta.LOCATION) });

module.exports = db;
<|endoftext|>


<commit_before>"use strict";

const IBase = require("./IBase");
const Utils = require("../core/Utils");
const Message = require("../models/Message");

const rest = require("../networking/rest");

/**
 * @interface
 * @model Message
 * @extends IBase
 */
class IMessage extends IBase {
  constructor(discordie, messageId) {
    super(Message, (key) =>
      this._discordie._messages.get(this._messageId)[key]
    );
    this._discordie = discordie;
    this._messageId = messageId;
    this._suppressErrors = true;
    Utils.privatify(this);

    /**
     * @readonly
     * @instance
     * @memberOf IMessage
     * @name author
     * @returns {IUser}
     */
    this._setValueOverride("author", v =>
      v ? this._discordie.Users.get(v.id) : null
    );

    /**
     * @readonly
     * @instance
     * @memberOf IMessage
     * @name mentions
     * @returns {Array<IUser>}
     */
    this._setValueOverride("mentions", v =>
      v ? v.map(u => this._discordie.Users.get(u.id)) : []
    );

    Object.freeze(this);
  }

  /**
   * Checks whether this message is cached.
   * @param {IMessage} message
   * @returns {boolean}
   * @readonly
   */
  get isCached() {
    return this.author != null;
  }
  get isEdited() {
    return this.isCached ? this.editedTimestamp != null : null;
  }

  get isPrivate() {
    return this._discordie._channels.isPrivate(this.channel_id);
  }
  get channel() {
    if (this.isPrivate) {
      return this._discordie.DirectMessageChannels.get(this.channel_id);
    }
    return this._discordie.Channels.get(this.channel_id);
  }
  get guild() {
    return this.channel ? this.channel.guild : null;
  }
  get member() {
    if (this.isPrivate || !this.isCached) return null;
    return this._discordie.Users.getMember(this.guild.id, this.author.id);
  }
  get edits() {
    return this._discordie._messages.getEdits(this.id);
  }

  /**
   * Makes a request to edit this message.
   * @param {String} content
   * @returns {Promise<IMessage, Error>}
   */
  edit(content) {
    return new Promise((rs, rj) => {
      rest(this._discordie)
        .channels.patchMessage(this.channel_id, this.id, content)
      .then(() => rs(this))
      .catch(rj);
    });
  }

  delete() {
    return rest(this._discordie)
      .channels.deleteMessage(this.channel_id, this.id);
  }

  /**
   * Makes a request to send a reply to channel the message was from, prefixing
   * content with author's mention in non-private channels.
   * @param {String} content
   * @param {boolean} [tts]
   * @returns {Promise<IMessage, Error>}
   */
  reply(content, mentions, tts) {
    // mentions array no longer required
    tts = (mentions === true || tts) ? true : false;
    if (this.isPrivate) {
      return this.channel.sendMessage(content, tts);
    }
    return this.channel
      .sendMessage(`${this.author.mention}, ${content}`, tts);
  }
}

module.exports = IMessage;
<commit_msg>Docs for IMessage<commit_after>"use strict";

const IBase = require("./IBase");
const Utils = require("../core/Utils");
const Message = require("../models/Message");

const rest = require("../networking/rest");

/**
 * @interface
 * @model Message
 * @extends IBase
 */
class IMessage extends IBase {
  constructor(discordie, messageId) {
    super(Message, (key) =>
      this._discordie._messages.get(this._messageId)[key]
    );
    this._discordie = discordie;
    this._messageId = messageId;
    this._suppressErrors = true;
    Utils.privatify(this);

    /**
     * @readonly
     * @instance
     * @memberOf IMessage
     * @name author
     * @returns {IUser}
     */
    this._setValueOverride("author", v =>
      v ? this._discordie.Users.get(v.id) : null
    );

    /**
     * @readonly
     * @instance
     * @memberOf IMessage
     * @name mentions
     * @returns {Array<IUser>}
     */
    this._setValueOverride("mentions", v =>
      v ? v.map(u => this._discordie.Users.get(u.id)) : []
    );

    Object.freeze(this);
  }

  /**
   * Checks whether this message is cached.
   * @param {IMessage} message
   * @returns {boolean}
   * @readonly
   */
  get isCached() {
    return this.author != null;
  }

  /**
   * Checks whether this message was edited by the author.
   * Returns null if message does not exist in cache.
   * @returns {boolean|null}
   * @readonly
   */
  get isEdited() {
    return this.isCached ? this.editedTimestamp != null : null;
  }

  /**
   * Checks whether this message is from a private channel (direct message).
   * Returns null if message/channel does not exist in cache.
   * @returns {boolean|null}
   * @readonly
   */
  get isPrivate() {
    return this._discordie._channels.isPrivate(this.channel_id);
  }

  /**
   * Gets channel of this message.
   * Returns null if message does not exist in cache.
   * @returns {IChannel|IDirectMessageChannel|null}
   * @readonly
   */
  get channel() {
    if (this.isPrivate) {
      return this._discordie.DirectMessageChannels.get(this.channel_id);
    }
    return this._discordie.Channels.get(this.channel_id);
  }

  /**
   * Gets guild of this message.
   * Returns null if message does not exist in cache.
   * @returns {IGuild|null}
   * @readonly
   */
  get guild() {
    return this.channel ? this.channel.guild : null;
  }

  /**
   * Gets member instance of author.
   * Returns null for private channels or if message does not exist in cache.
   * @returns {IGuildMember|null}
   * @readonly
   */
  get member() {
    if (this.isPrivate || !this.isCached) return null;
    return this._discordie.Users.getMember(this.guild.id, this.author.id);
  }

  /**
   * Creates an array of all known (cached) versions of this message (including
   * the latest).
   * Sorted from latest (first) to oldest (last).
   * Not guaranteed to have embeds.
   * @returns {Array<Object>}
   */
  get edits() {
    return this._discordie._messages.getEdits(this.id);
  }

  /**
   * Makes a request to edit this message.
   * @param {String} content
   * @returns {Promise<IMessage, Error>}
   */
  edit(content) {
    return new Promise((rs, rj) => {
      rest(this._discordie)
        .channels.patchMessage(this.channel_id, this.id, content)
      .then(() => rs(this))
      .catch(rj);
    });
  }

  /**
   * Makes a request to delete this message.
   * @returns {Promise}
   */
  delete() {
    return rest(this._discordie)
      .channels.deleteMessage(this.channel_id, this.id);
  }

  /**
   * Makes a request to send a reply to channel the message was from, prefixing
   * content with author's mention in non-private channels.
   * @param {String} content
   * @param {boolean} [tts]
   * @returns {Promise<IMessage, Error>}
   */
  reply(content, mentions, tts) {
    // mentions array no longer required
    tts = (mentions === true || tts) ? true : false;
    if (this.isPrivate) {
      return this.channel.sendMessage(content, tts);
    }
    return this.channel
      .sendMessage(`${this.author.mention}, ${content}`, tts);
  }
}

module.exports = IMessage;
<|endoftext|>


<commit_before>'use strict';

var util           = require('util');
var _              = require('lodash');
var EventEmitter   = require('events').EventEmitter;
var Coyote         = require('coyote');
var Route          = require('./Route');
var ClusteredRoute = require('./ClusteredRoute');

/**
 * Create a new Harmonia Server instance
 *
 * @param {string} type One of 'rpc' or 'worker'
 * @param {object} options
 */
function Server(type, options) {
  EventEmitter.call(this);

  if (type && (type !== 'rpc' && type !== 'worker')) {
    throw new Error('Invalid server type');
  }

  this.type    = type || 'rpc';
  this.options = _.defaults(options || {}, {
    cluster : true
  });

  this.routes = {};
}

util.inherits(Server, EventEmitter);

Server.prototype.route = function(routeConfig) {
  var RouteClass = this.options.cluster ? ClusteredRoute : Route;
  var route = new RouteClass(routeConfig);

  if (this.routes[route.method]) {
    throw new Error('Conflicting method name');
  }

  this.routes[route.method] = route;

  return this;
};

Server.prototype.listen = function(amqpUrl) {
  return Coyote.createConnection(amqpUrl)
    .bind(this)
    .then(function(connection) {
      this.connection = connection;

      var socketType = this.type === 'rpc' ? 'REPLY' : 'WORKER';
      _.each(this.routes, function(route) {
        route.start(this.connection, socketType);
      }.bind(this));
    });
};

Server.prototype.pause = function() {
  this.coyote.pause();
  return this;
};

Server.prototype.resume = function() {
  this.coyote.resume();
  return this;
};

Server.prototype.shutdown = function() {
  this.coyote.shutdown();
  return this;
};

module.exports = Server;
<commit_msg>remove option to disable clustering<commit_after>'use strict';

var util           = require('util');
var _              = require('lodash');
var EventEmitter   = require('events').EventEmitter;
var Coyote         = require('coyote');
var ClusteredRoute = require('./ClusteredRoute');

/**
 * Create a new Harmonia Server instance
 *
 * @param {string} type One of 'rpc' or 'worker'
 * @param {object} options
 */
function Server(type) {
  EventEmitter.call(this);

  if (type && (type !== 'rpc' && type !== 'worker')) {
    throw new Error('Invalid server type');
  }

  this.type   = type || 'rpc';
  this.routes = {};
}

util.inherits(Server, EventEmitter);

Server.prototype.route = function(routeConfig) {
  var route = new ClusteredRoute(routeConfig);

  if (this.routes[route.method]) {
    throw new Error('Conflicting method name');
  }

  this.routes[route.method] = route;

  return this;
};

Server.prototype.listen = function(amqpUrl) {
  return Coyote.createConnection(amqpUrl)
    .bind(this)
    .then(function(connection) {
      this.connection = connection;

      var socketType = this.type === 'rpc' ? 'REPLY' : 'WORKER';
      _.each(this.routes, function(route) {
        route.start(this.connection, socketType);
      }.bind(this));
    });
};

Server.prototype.pause = function() {
  this.coyote.pause();
  return this;
};

Server.prototype.resume = function() {
  this.coyote.resume();
  return this;
};

Server.prototype.shutdown = function() {
  this.coyote.shutdown();
  return this;
};

module.exports = Server;
<|endoftext|>


<commit_before>var JSONAPIClient = require('json-api-client');
var Model = JSONAPIClient.Model;
var makeHTTPRequest = JSONAPIClient.makeHTTPRequest;
var ls = require('local-storage');

var config = require('./config');
var apiClient = require('./api-client');

// Use this to override the default API-specific headers.
var JSON_HEADERS = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
};

// We don't want to wait until the token is already expired before refreshing it.
var TOKEN_EXPIRATION_ALLOWANCE = 60 * 1000;

// Save local storage stuff as something totally obvious
var LOCAL_STORAGE_PREFIX = 'panoptesClientOAuth_';

// Create our model, then do stuff with it later
module.exports = new Model({
  _bearerRefreshTimeout: NaN,
  _clientAppId: ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId'),
  _currentSessionCheckPromise: null,
  _currentUserPromise: null,
  _tokenDetails: null,

  checkCurrent: function() {
    console.log('Checking current user');

    // If we're checking for an existing session already, defer this until
    // it's finished
    var initialCheck = this._currentSessionCheckPromise || Promise.resolve();

    if (!this._currentUserPromise) {
      this.update({
        _currentUserPromise: initialCheck
          .then(this._getSession)
          .catch(function() {
            // Nobody's signed in. This isn't an error.
            console.info('No current user');
            return null;
          }),
      });
    }

    return this._currentUserPromise;
  },

  init: function (appID) {
    return new Promise(function(resolve, reject) {

      // Don't init if we're in an iFrame, as we could be the token refresh process
      if (window.frameElement) {
        return false;
      }

      console.log('Using OAuth (implicit grant) for login');
      console.info('Setting app ID to', appID);
      ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId', appID);
      this._clientAppId = appID;

      // Handle new token details if we've completed a sign in
      if (checkUrlForToken(window.location.href)) {
        console.log('Token found in URL');
        var tokenDetails = parseUrl(window.location.href);
        this._handleBearerToken(tokenDetails);
        sessionStorage.setItem(LOCAL_STORAGE_PREFIX + 'tokenDetails', JSON.stringify(tokenDetails));

        // And redirect to the desired page
        var url = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
        location.assign(url);
      }

      // If not, let's try and pick up an existing Panoptes session anyway
      this._checkForPanoptesSession()
        .then(function(tokenDetails) {
          this._handleBearerToken(tokenDetails);
          resolve(tokenDetails)
        }.bind(this))
        .catch(function (error) {
          // We probably haven't signed in before
          console.info(error);
          resolve(null);
        });

    }.bind(this));

  },

  signIn: function(redirectUri) {
    console.log('Signing in with OAuth');
    var originalArguments = arguments;
    return this.checkCurrent()
      .then(function (token) {
        if (token) {
          return this.signOut().then(function() {
            return this.signIn.apply(this, originalArguments);
          }.bind(this));
        } else {
          this._saveRedirectUri(redirectUri);
          location.assign(this._createOAuthUrl(redirectUri));
        }
      }.bind(this));
  },

  signOut: function() {
    console.log('Signing out');
    return this.checkCurrent().then(function(user) {
      if (user) {
        return this._getAuthToken().then(function(token) {
          var url = config.oauthHost + '/users/sign_out';

          var deleteHeaders = Object.create(JSON_HEADERS);
          deleteHeaders['X-CSRF-Token'] = token;

          return makeHTTPRequest('DELETE', url, null, deleteHeaders)
            .then(function() {
              this._deleteBearerToken();
              this.update({
                _currentUserPromise: Promise.resolve(null),
              });
              console.info('Signed out');
              return null;
            }.bind(this))
            .catch(function(request) {
              console.error('Failed to sign out');
              return apiClient.handleError(request);
            }.bind(this));
        }.bind(this));
      } else {
        throw new Error('Failed to sign out; not signed in');
      }
    }.bind(this));
  },

  _createOAuthUrl: function(redirectUri) {
    if (!this._clientAppId) {
      throw new Error('Client app ID not set');
    }

    return [
      config.oauthHost,
      '/oauth/authorize',
      '?response_type=token',
      '&client_id=',
      this._clientAppId,
      '&redirect_uri=',
      redirectUri
    ].join('');
  },

  _checkForPanoptesSession: function() {
    var sessionTokenDetails = JSON.parse(sessionStorage.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
    var redirectUri = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
    this.update({
      _currentSessionCheckPromise: new Promise(function(resolve, reject) {
        if (sessionTokenDetails) {
          resolve(sessionTokenDetails);
        }

        if (!redirectUri) {
          reject(Error('No redirect URI found'));
        }

        // Create a new iFrame
        var url = this._createOAuthUrl(redirectUri);
        this._iframe = createIFrame(url);

        // Try and get the token details from our iFrame. If it throws an error,
        // it's because we're being redirected to the signin page (and therefore
        // there is no session) - so we should also replace the security error
        // with a more relevant one.
        this._iframe.onload = function() {
          try {
            var newUrl = this._iframe.contentWindow.location.href;
            if (checkUrlForToken(newUrl)) {
              console.info('Found existing Panoptes session');
              var newTokenDetails = parseUrl(newUrl);
              resolve(newTokenDetails);
            } else {
              throw new TypeError('Valid OAuth details not found in URL');
            }
          } catch (error) {
            if (error.name === 'SecurityError') {
              error = new Error('No existing Panoptes session found');
            }
            reject(error);
          } finally {
            this._iframe = destroyIFrame(this._iframe);
          }
        }.bind(this);
      }.bind(this))
    });

    return this._currentSessionCheckPromise;
  },

  _deleteBearerToken: function() {
    console.log('Deleting bearer token');
    this._tokenDetails = null;
    delete apiClient.headers.Authorization;
    sessionStorage.removeItem(LOCAL_STORAGE_PREFIX + 'tokenDetails');
  },

  _getAuthToken: function() {
    console.log('Getting auth token');
    var url = config.oauthHost + '/users/sign_in/?now=' + Date.now();
    return makeHTTPRequest('GET', url, null, JSON_HEADERS)
      .then(function(response) {
        var authToken = response.header['x-csrf-token'];
        console.info('Got auth token', authToken.slice(-6));
        return authToken;
      })
      .catch(function(response) {
        console.error('Failed to get auth token');
        apiClient.handleError(response);
      });
  },

  _getSession: function() {
    console.log('Getting session');
    return apiClient.get('/me')
      .then(function(users) {
        var user = users[0];
        console.info('Got session', user.login, user.id);
        return user;
      })
      .catch(function(error) {
        console.error('Failed to get session');
        throw error;
      });
  },

  _handleBearerToken: function(tokenDetails) {
    console.log('Got new bearer token', tokenDetails.access_token.slice(-6));
    this._tokenDetails = tokenDetails;
    apiClient.headers.Authorization = 'Bearer ' + tokenDetails.access_token;

    var refresh = this._refreshBearerToken.bind(this);
    var timeToRefresh = (tokenDetails.expires_in * 1000) - TOKEN_EXPIRATION_ALLOWANCE;
    this._bearerRefreshTimeout = setTimeout(refresh, timeToRefresh);
    return tokenDetails;
  },

  _refreshBearerToken: function() {
    return this._checkForPanoptesSession()
      .then(function(tokenDetails) {
        sessionStorage.setItem(LOCAL_STORAGE_PREFIX + 'tokenDetails', JSON.stringify(tokenDetails));
        return this._handleBearerToken(tokenDetails);
      }.bind(this))
      .catch(function (error) {
        console.info('Panoptes session has expired');
      });
  },

  _saveRedirectUri: function(redirectUri) {
    console.info('Saving redirectUri:', redirectUri);
    ls.set(LOCAL_STORAGE_PREFIX + 'redirectUri', redirectUri);
  }

});

// Utility functions
function checkUrlForToken(string) {
  return string.indexOf('access_token') !== -1 &&
    string.indexOf('token_type=bearer') !== -1;
}

function parseUrl(string) {
  return {
    access_token: string.match(/access_token=([\w\-\.]+)/)[1],
    token_type: string.match(/token_type=(\w+)/)[1],
    expires_in: string.match(/expires_in=(\d+)/)[1],
  };
}

function isTokenStillValid(tokenDetails) {
  return (tokenDetails.started_at + tokenDetails.expires_in) > Date.now();
}

function createIFrame(url) {
  var iframe = document.createElement('iframe');
  iframe.setAttribute('src', url);
  iframe.setAttribute('style', 'display: none;');
  document.body.appendChild(iframe);
  return iframe;
}

function destroyIFrame(iframe) {
  iframe.parentNode.removeChild(iframe);
  return null;
}
<commit_msg>Don't swallow cross-domain security errors Console warn if the iframe fails, rather than swallowing the original error and throwing a new one.<commit_after>var JSONAPIClient = require('json-api-client');
var Model = JSONAPIClient.Model;
var makeHTTPRequest = JSONAPIClient.makeHTTPRequest;
var ls = require('local-storage');

var config = require('./config');
var apiClient = require('./api-client');

// Use this to override the default API-specific headers.
var JSON_HEADERS = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
};

// We don't want to wait until the token is already expired before refreshing it.
var TOKEN_EXPIRATION_ALLOWANCE = 60 * 1000;

// Save local storage stuff as something totally obvious
var LOCAL_STORAGE_PREFIX = 'panoptesClientOAuth_';

// Create our model, then do stuff with it later
module.exports = new Model({
  _bearerRefreshTimeout: NaN,
  _clientAppId: ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId'),
  _currentSessionCheckPromise: null,
  _currentUserPromise: null,
  _tokenDetails: null,

  checkCurrent: function() {
    console.log('Checking current user');

    // If we're checking for an existing session already, defer this until
    // it's finished
    var initialCheck = this._currentSessionCheckPromise || Promise.resolve();

    if (!this._currentUserPromise) {
      this.update({
        _currentUserPromise: initialCheck
          .then(this._getSession)
          .catch(function() {
            // Nobody's signed in. This isn't an error.
            console.info('No current user');
            return null;
          }),
      });
    }

    return this._currentUserPromise;
  },

  init: function (appID) {
    return new Promise(function(resolve, reject) {

      // Don't init if we're in an iFrame, as we could be the token refresh process
      if (window.frameElement) {
        return false;
      }

      console.log('Using OAuth (implicit grant) for login');
      console.info('Setting app ID to', appID);
      ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId', appID);
      this._clientAppId = appID;

      // Handle new token details if we've completed a sign in
      if (checkUrlForToken(window.location.href)) {
        console.log('Token found in URL');
        var tokenDetails = parseUrl(window.location.href);
        this._handleBearerToken(tokenDetails);
        sessionStorage.setItem(LOCAL_STORAGE_PREFIX + 'tokenDetails', JSON.stringify(tokenDetails));

        // And redirect to the desired page
        var url = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
        location.assign(url);
      }

      // If not, let's try and pick up an existing Panoptes session anyway
      this._checkForPanoptesSession()
        .then(function(tokenDetails) {
          this._handleBearerToken(tokenDetails);
          resolve(tokenDetails)
        }.bind(this))
        .catch(function (error) {
          // We probably haven't signed in before
          console.info(error);
          resolve(null);
        });

    }.bind(this));

  },

  signIn: function(redirectUri) {
    console.log('Signing in with OAuth');
    var originalArguments = arguments;
    return this.checkCurrent()
      .then(function (token) {
        if (token) {
          return this.signOut().then(function() {
            return this.signIn.apply(this, originalArguments);
          }.bind(this));
        } else {
          this._saveRedirectUri(redirectUri);
          location.assign(this._createOAuthUrl(redirectUri));
        }
      }.bind(this));
  },

  signOut: function() {
    console.log('Signing out');
    return this.checkCurrent().then(function(user) {
      if (user) {
        return this._getAuthToken().then(function(token) {
          var url = config.oauthHost + '/users/sign_out';

          var deleteHeaders = Object.create(JSON_HEADERS);
          deleteHeaders['X-CSRF-Token'] = token;

          return makeHTTPRequest('DELETE', url, null, deleteHeaders)
            .then(function() {
              this._deleteBearerToken();
              this.update({
                _currentUserPromise: Promise.resolve(null),
              });
              console.info('Signed out');
              return null;
            }.bind(this))
            .catch(function(request) {
              console.error('Failed to sign out');
              return apiClient.handleError(request);
            }.bind(this));
        }.bind(this));
      } else {
        throw new Error('Failed to sign out; not signed in');
      }
    }.bind(this));
  },

  _createOAuthUrl: function(redirectUri) {
    if (!this._clientAppId) {
      throw new Error('Client app ID not set');
    }

    return [
      config.oauthHost,
      '/oauth/authorize',
      '?response_type=token',
      '&client_id=',
      this._clientAppId,
      '&redirect_uri=',
      redirectUri
    ].join('');
  },

  _checkForPanoptesSession: function() {
    var sessionTokenDetails = JSON.parse(sessionStorage.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
    var redirectUri = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
    this.update({
      _currentSessionCheckPromise: new Promise(function(resolve, reject) {
        if (sessionTokenDetails) {
          resolve(sessionTokenDetails);
        }

        if (!redirectUri) {
          reject(Error('No redirect URI found'));
        }

        // Create a new iFrame
        var url = this._createOAuthUrl(redirectUri);
        this._iframe = createIFrame(url);

        // Try and get the token details from our iFrame. If it throws an error,
        // it's because we're being redirected to the signin page (and therefore
        // there is no session) - so we should also replace the security error
        // with a more relevant one.
        this._iframe.onload = function() {
          try {
            var newUrl = this._iframe.contentWindow.location.href;
            if (checkUrlForToken(newUrl)) {
              console.info('Found existing Panoptes session');
              var newTokenDetails = parseUrl(newUrl);
              resolve(newTokenDetails);
            } else {
              throw new TypeError('Valid OAuth details not found in URL');
            }
          } catch (error) {
            if (error.name === 'SecurityError') {
              console.warn('No existing Panoptes session found');
            }
            reject(error);
          } finally {
            this._iframe = destroyIFrame(this._iframe);
          }
        }.bind(this);
      }.bind(this))
    });

    return this._currentSessionCheckPromise;
  },

  _deleteBearerToken: function() {
    console.log('Deleting bearer token');
    this._tokenDetails = null;
    delete apiClient.headers.Authorization;
    sessionStorage.removeItem(LOCAL_STORAGE_PREFIX + 'tokenDetails');
  },

  _getAuthToken: function() {
    console.log('Getting auth token');
    var url = config.oauthHost + '/users/sign_in/?now=' + Date.now();
    return makeHTTPRequest('GET', url, null, JSON_HEADERS)
      .then(function(response) {
        var authToken = response.header['x-csrf-token'];
        console.info('Got auth token', authToken.slice(-6));
        return authToken;
      })
      .catch(function(response) {
        console.error('Failed to get auth token');
        apiClient.handleError(response);
      });
  },

  _getSession: function() {
    console.log('Getting session');
    return apiClient.get('/me')
      .then(function(users) {
        var user = users[0];
        console.info('Got session', user.login, user.id);
        return user;
      })
      .catch(function(error) {
        console.error('Failed to get session');
        throw error;
      });
  },

  _handleBearerToken: function(tokenDetails) {
    console.log('Got new bearer token', tokenDetails.access_token.slice(-6));
    this._tokenDetails = tokenDetails;
    apiClient.headers.Authorization = 'Bearer ' + tokenDetails.access_token;

    var refresh = this._refreshBearerToken.bind(this);
    var timeToRefresh = (tokenDetails.expires_in * 1000) - TOKEN_EXPIRATION_ALLOWANCE;
    this._bearerRefreshTimeout = setTimeout(refresh, timeToRefresh);
    return tokenDetails;
  },

  _refreshBearerToken: function() {
    return this._checkForPanoptesSession()
      .then(function(tokenDetails) {
        sessionStorage.setItem(LOCAL_STORAGE_PREFIX + 'tokenDetails', JSON.stringify(tokenDetails));
        return this._handleBearerToken(tokenDetails);
      }.bind(this))
      .catch(function (error) {
        console.info('Panoptes session has expired');
      });
  },

  _saveRedirectUri: function(redirectUri) {
    console.info('Saving redirectUri:', redirectUri);
    ls.set(LOCAL_STORAGE_PREFIX + 'redirectUri', redirectUri);
  }

});

// Utility functions
function checkUrlForToken(string) {
  return string.indexOf('access_token') !== -1 &&
    string.indexOf('token_type=bearer') !== -1;
}

function parseUrl(string) {
  return {
    access_token: string.match(/access_token=([\w\-\.]+)/)[1],
    token_type: string.match(/token_type=(\w+)/)[1],
    expires_in: string.match(/expires_in=(\d+)/)[1],
  };
}

function isTokenStillValid(tokenDetails) {
  return (tokenDetails.started_at + tokenDetails.expires_in) > Date.now();
}

function createIFrame(url) {
  var iframe = document.createElement('iframe');
  iframe.setAttribute('src', url);
  iframe.setAttribute('style', 'display: none;');
  document.body.appendChild(iframe);
  return iframe;
}

function destroyIFrame(iframe) {
  iframe.parentNode.removeChild(iframe);
  return null;
}
<|endoftext|>


<commit_before>/**
 * Implementation of some of Drupal's database layer in Node.js.
 *
 * Ie. stuff that lives in database.inc in Drupal.
 */

var active_db,
    mysql = require('mysql');

/**
 * db_connect() as in Drupal 6.
 *
 * This is much more complex with PDO in Drupal 7, which we won't try to
 * emulate here.
 */
function connect(options, callback) {
  // A shortcut. Currently we only support one active db.
  if (active_db) {
    if (callback) {
      callback(null, active_db);
    }

    return active_db;
  }

  var client = new mysql.Client(options);

  client.connect(function (err) {
    active_db = client;

    if (callback) {
      if (err) {
        callback(err, null);
      }

      callback(null, active_db);
    }
  });
}

/**
 * db_query() as in Drupal (though callback-based).
 *
 * This function assumes that db_connect() has done its work, so be sure
 * to call it and wait for its callback first.
 */
function query(queryString, args, callback) {
  if (!active_db) {
    callback('No active database connection for query.');
  }

  active_db.query(queryString, args, callback);
}

module.exports = {
  active_db: active_db,
  connect: connect,
  query: query
};

<commit_msg>Handle escaping object type variables better.<commit_after>/**
 * Implementation of some of Drupal's database layer in Node.js.
 *
 * Ie. stuff that lives in database.inc in Drupal.
 */

var active_db,
    mysql = require('mysql');

/**
 * db_connect() as in Drupal 6.
 *
 * This is much more complex with PDO in Drupal 7, which we won't try to
 * emulate here.
 */
function connect(options, callback) {
  // A shortcut. Currently we only support one active db.
  if (active_db) {
    if (callback) {
      callback(null, active_db);
    }

    return active_db;
  }

  // Custom override. See https://github.com/felixge/node-mysql/issues/82 .
  mysql.Client.prototype.escape = function(val) {
    var object = false;
    if (val === undefined || val === null) {
      return 'NULL';
    }

    switch (typeof val) {
      case 'boolean': return (val) ? 'true' : 'false';
      case 'number': return val+'';
    }

    if (typeof val === 'object') {
      val = val.toString();
      object = true;
    }

    val = val.replace(/[\0\n\r\b\t\\\'\"\x1a]/g, function(s) {
      switch(s) {
        case "\0": return "\\0";
        case "\n": return "\\n";
        case "\r": return "\\r";
        case "\b": return "\\b";
        case "\t": return "\\t";
        case "\x1a": return "\\Z";
        default: return "\\"+s;
      }
    });

    if (object) {
      return val;
    }

    return "'"+val+"'";
  };

  var client = new mysql.Client(options);

  client.connect(function (err) {
    active_db = client;

    if (callback) {
      if (err) {
        callback(err, null);
      }

      callback(null, active_db);
    }
  });
}

/**
 * db_query() as in Drupal (though callback-based).
 *
 * This function assumes that db_connect() has done its work, so be sure
 * to call it and wait for its callback first.
 */
function query(queryString, args, callback) {
  if (!active_db) {
    callback('No active database connection for query.');
  }

  active_db.query(queryString, args, callback);
}

module.exports = {
  active_db: active_db,
  connect: connect,
  query: query
};

<|endoftext|>