Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- var sha1 = require("./sha1").hex;
- var http = require("http");
- var mysql = require('./mysql');
- var util = require("../js/util");
- var settings = require("./settings");
- function JSONResponse(database, request, response)
- {
- var self = this;
- this._user = undefined;
- this._database = database;
- this._request = request;
- this._response = response;
- this._JSONObject = {};
- this._time = new Date();
- this._rawResponse = null;
- this._cookie = {};
- var rawcookie = request.headers.cookie
- ?request.headers.cookie.split(/;\s*/)
- :[];
- for(var i=0; i<rawcookie.length; i++)
- {
- rawcookie[i] = rawcookie[i].split("=");
- this._cookie[rawcookie[i][0]] = unescape(rawcookie[i][1]);
- }
- this._params = request.url.substr(1).split("/");
- console.log(request.headers["x-forwarded-for"]+": "
- +this._params.join(" "));
- response.writeHead(200,{"Content-Type": "application/json"});
- this.getTime = function()
- {
- return self._time;
- }
- this.getCookie = function(key)
- {
- return self._cookie[key];
- }
- /**
- * Get the currently logged in user. Calls the callback method with the user
- * that is logged in, or with NULL if there is no login.
- * @param cb The callback function.
- */
- this.validateLogin = function(cb)
- {
- if(this._user !== undefined)
- {
- cb(self._user);
- return;
- }
- if(!self.getCookie("login"))
- {
- self._user = null;
- cb(self._user);
- return;
- }
- var loginstr = self.getCookie("login").split(",");
- var userId = parseInt(loginstr[0]);
- var passHash = loginstr[1];
- self._database.getUser(userId, function(user)
- {
- user.checkPassword(passHash, function(isValid)
- {
- self._user = isValid?user:null;
- cb(self._user);
- });
- });
- }
- this.addError = function(message)
- {
- if(!(self._JSONObject.errors instanceof Array))
- self._JSONObject.errors = new Array();
- self._JSONObject.errors.push(message);
- }
- this.getParam = function(i)
- {
- return self._params[i];
- }
- this.getParams = function()
- {
- return self._params;
- }
- this.getUser = function()
- {
- return self._user;
- }
- this.setRawResponse = function(data)
- {
- self._rawResponse = data;
- }
- this.getRawResponse = function()
- {
- return JSON.stringify(self._JSONObject);
- }
- this.set = function(key, value)
- {
- self._JSONObject[key] = value;
- }
- this.get = function(key)
- {
- return self._JSONObject[key];
- }
- this.end = function()
- {
- if(self._rawResponse)
- self._response.end(self._rawResponse);
- else
- self._response.end(self.getRawResponse());
- }
- }
- function Database(sqlClient)
- {
- var self = this;
- /** @var The MySQL Client connection. */
- this._sql = sqlClient;
- /** @var A cache with Product-objects. */
- this._products = {};
- /** @var A cache with User-objects. */
- this._users = {};
- /** @var A cache with Bid-objects. */
- this._bids = {};
- /** @var A reference to the last bid. */
- this._lastBid = null;
- this._init = function(cb)
- {
- self.getProductsStartingBetween(new Date(0), new Date(), function()
- {
- self._init = undefined;
- setInterval(function()
- {
- var ids = [];
- var x = null;
- ids.clear();
- for(x in self._products)
- ids.push(x);
- console.log("[Cache<Product> "+ids.toString()+"]");
- ids.clear();
- for(x in self._users)
- ids.push(x);
- console.log("[Cache<User> "+ids.toString()+"]");
- ids.clear();
- for(x in self._bids)
- ids.push(x);
- console.log("[Cache<Bid> "+ids.toString()+"]");
- var mem = process.memoryUsage();
- console.log("[Heap: "+Math.round(mem.heapUsed/1024)+"k used, "
- +Math.round(mem.heapTotal/1024)+"k reserved]");
- },30000);
- if(cb)cb(self);
- });
- }
- /** @var An array with on-bid-placed-event listeners. */
- this._onBidPlacedEventListeners = [];
- this._onProductLoadedEventListeners = [];
- /**
- *
- */
- this._triggerOnBidPlacedEventListeners = function(bid)
- {
- for(var i=0; i<self._onBidPlacedEventListeners.length; i++)
- self._onBidPlacedEventListeners[i](bid);
- }
- this._triggerOnProductLoadedEventListeners = function(product)
- {
- for(var i=0; i<self._onProductLoadedEventListeners.length; i++)
- self._onProductLoadedEventListeners[i](product);
- }
- this.addProductLoadedEventListener = function(cb)
- {
- self._onProductLoadedEventListeners.add(cb);
- }
- this.removeProductLoadedEventListener = function(cb)
- {
- self._onProductLoadedEventListeners.remove(cb);
- }
- /**
- * Add a on-bid-placed-event-listener. The function will be called everytime
- * a bid is placed on any product. The callback will be given a
- * Bid-object which the bid was placed on as a first parameter.
- * @param cb The event-listener.
- */
- this.addOnBidPlacedEventListener = function(cb)
- {
- self._onBidPlacedEventListeners.add(cb);
- }
- this.removeOnBidPlacedEventListener = function(cb)
- {
- self._onBidPlacedEventListeners.remove(cb);
- }
- this.query = function(sql, params, cb)
- {
- self._sql.query(sql, params, function(err, results)
- {
- if(err)
- {
- console.log("ERROR EXECUTING QUERY:");
- console.log(sql);
- console.log(self._sql);
- throw err;
- }
- if(cb)cb(results);
- });
- }
- this.uncacheBid = function(bid)
- {
- delete self._bids[bid.getId()];
- var c = 0;
- for(var x in self._bids)
- c++;
- console.log("Purged Bid #"
- +bid.getId()+" on "
- +bid.getProduct().getTitle());
- }
- this.getLastBid = function()
- {
- return self._lastBid;
- }
- this.getProductsStartingBetween = function(start, end, cb)
- {
- self.query("SELECT id FROM products WHERE UNIX_TIMESTAMP(timeStart) "
- +"BETWEEN ? AND ?",
- [start.getTime()/1000, end.getTime()/1000],
- function(results)
- {
- var products = new Array();
- var productsLoadedCallback = function()
- {
- if(products.length < results.length)
- return;
- if(cb)cb(products);
- }
- for(var i=0; i<results.length; i++)
- {
- self.getProduct(results[i].id, function(product)
- {
- products.push(product);
- productsLoadedCallback();
- });
- }
- });
- }
- this.getLastBidsPlacedAfterBidId = function(bidId, cb)
- {
- self.query("SELECT MAX(id) AS id FROM bids WHERE id > ?"
- +" GROUP BY product",
- [parseInt(bidId)],
- function(results)
- {
- var bids = new Array();
- var bidsLoadedCallback = function()
- {
- if(bids.length < results.length)
- return;
- if(cb)cb(bids);
- }
- for(var i=0; i<results.length; i++)
- {
- self.getBid(results[i].id, function(bid)
- {
- bids.push(bid);
- bidsLoadedCallback();
- });
- }
- });
- }
- this.getLastBidsPlacedBetween = function(start, end, cb)
- {
- self.query("SELECT bid.id AS id FROM bids AS bid "
- +"JOIN products AS product ON bid.product = product.id "
- +"WHERE UNIX_TIMESTAMP(product.timeStart)+bid.centiTime/100 "
- +"BETWEEN ? AND ? GROUP BY product.id ORDER BY "
- +"bid.id DESC",
- [start.getTime()/1000, end.getTime()/1000],
- function(results)
- {
- var bids = new Array();
- var bidsLoadedCallback = function()
- {
- if(bids.length < results.length)
- return;
- if(cb)cb(bids);
- }
- for(var i=0; i<results.length; i++)
- {
- self.getBid(results[i].id, function(bid)
- {
- bids.push(bid);
- bidsLoadedCallback();
- });
- }
- });
- }
- /**
- * Get a product from this database. Calls a callback method with the
- * retrieved Product-object on completion, or with NULL if the Product
- * doesn't exist.
- * @param id The id of the product to get.
- * @param cb The callback.
- */
- this.getProduct = function(id, cb)
- {
- id = parseInt(id);
- if(self._products[id] !== undefined)
- {
- if(cb)cb(self._products[id]);
- return;
- }
- self.query("SELECT title, UNIX_TIMESTAMP(timeStart) AS time, thumbnail,"
- +"value, bidDurationMinimum, bidDurationDecrementInterval, "
- +"bidDurationDecrementAmount, bidDurationStart, "
- +"bidValueIncrement FROM products WHERE id = ? LIMIT 1",
- [id],
- function(results)
- {
- if(self._products[id])
- {
- console.log("Tried to reload "+self._products[id]);
- if(cb)cb(self._products[id]);
- return;
- }
- if(results.length == 0)
- {
- self._products[id] = null;
- console.log("Loaded "+self._products[id]);
- if(cb)cb(self._products[id]);
- return;
- }
- self._products[id]
- = new Product(self, id,
- results[0].title,
- results[0].thumbnail,
- results[0].value,
- new Date(results[0].time*1000),
- results[0].bidDurationMinimum,
- results[0].bidDurationDecrementInterval,
- results[0].bidDurationDecrementAmount,
- results[0].bidDurationStart,
- results[0].bidValueIncrement);
- self._products[id].addOnBidPlacedEventListener(
- self._triggerOnBidPlacedEventListeners);
- self._products[id]._init(function(product)
- {
- console.log("Loaded "+product);
- self._triggerOnProductLoadedEventListeners(product);
- if(cb)cb(product);
- });
- });
- }
- /**
- * Get a user from this database. Calls a callback method with the retrieved
- * User-object on completion, or with NULL if there is no user with that id.
- * @param id The id of the user.
- * @param cb The callback.
- */
- this.getUser = function(id, cb)
- {
- id = parseInt(id);
- if(self._users[id] !== undefined)
- {
- if(cb)cb(self._users[id]);
- return;
- }
- self.query("SELECT username FROM users WHERE id = ? LIMIT 1",
- [id],
- function(results)
- {
- if(self._users[id])
- {
- console.log("Tried to reload "+self._users[id]);
- if(cb)cb(self._users[id]);
- return;
- }
- self._users[id] = results.length == 0
- ?null
- :new User(self, id, results[0].username);
- console.log("Loaded "+self._users[id]);
- if(cb)cb(self._users[id]);
- });
- }
- /**
- * Get a bid from this database. Calls a callback method with the retrieved
- * Bid-object on completion, or with NULL if there is no bid with that id.
- * @param id The id of the bid.
- * @param cb The callback.
- */
- this.getBid = function(id, cb)
- {
- id = parseInt(id);
- if(self._bids[id] !== undefined)
- {
- cb(self._bids[id]);
- return;
- }
- self.query("SELECT id, product, centiTime, user "
- +"FROM bids WHERE id = ? LIMIT 1",
- [id],
- function(results)
- {
- if(self._bids[id])
- {
- console.log("Tried to reload Bid #"+id+".");
- if(cb)cb(self._bids[id]);
- return;
- }
- if(results.length == 0)
- {
- self._bids[id] = null;
- console.log("Loaded "+self._bids[id]);
- if(cb)cb(self._bids[id]);
- return;
- }
- var bidUser = undefined;
- var bidProduct = undefined;
- var bidAdditionalDataCallback = function()
- {
- if(bidUser === undefined
- || bidProduct === undefined)
- return;
- if(self._bids[id])
- {
- console.log("Tried to reload "+self._bids[id]);
- if(cb)cb(self._bids[id]);
- return;
- }
- var time = new Date( bidProduct.getStartTime().getTime()
- + results[0].centiTime*10);
- self._bids[id] = new Bid(self, id, bidProduct,
- new Date(time), bidUser);
- self._bids[id]._init(function(bid)
- {
- console.log("Loaded "+bid);
- if(!self._lastBid
- || self._lastBid.getId() < bid.getId())
- self._lastBid = bid;
- if(cb)cb(bid);
- });
- }
- self.getUser(results[0].user, function(user)
- {
- bidUser = user;
- bidAdditionalDataCallback();
- });
- self.getProduct(results[0].product, function(product)
- {
- bidProduct = product;
- bidAdditionalDataCallback();
- });
- });
- }
- }
- function User(db, id, username)
- {
- var self = this;
- this._database = db;
- this._id = id;
- this._username = username;
- this.getId = function()
- {
- return self._id;
- }
- this.getUsername = function()
- {
- return self._username;
- }
- /**
- * Check a user's password. Calls a callback after checking is done. The
- * callback will be called with a boolean as a first parameter, containing
- * TRUE if the given passwordhash did match the password or FALSE otherwise.
- */
- this.checkPassword = function(passhash, cb)
- {
- self._database.query("SELECT COUNT(1) FROM users "
- +"WHERE id = ? AND password = UNHEX(?) LIMIT 1",
- [self.getId(), passhash],
- function(results)
- {
- if(cb)cb(results.length > 0);
- });
- }
- this.toString = function()
- {
- return "[User #"+self.getId()+" "
- +self.getUsername()+"]";
- };
- this.toJSON = function()
- {
- return {
- id: self.getId(),
- username: self.getUsername()
- };
- }
- }
- function Product(db, id, title, thumbnail, value, time,
- bidDurationMinimum,
- bidDurationDecrementInterval,
- bidDurationDecrementAmount,
- bidDurationStart,
- bidValueIncrement)
- {
- var self = this;
- this._database = db;
- this._id = id;
- this._thumbnail = thumbnail;
- this._value = value;
- this._title = title;
- this._time = time;
- this._bidDurationMinimum = bidDurationMinimum;
- this._bidDurationDecrementInterval = bidDurationDecrementInterval;
- this._bidDurationDecrementAmount = bidDurationDecrementAmount;
- this._bidDurationStart = bidDurationStart;
- this._bidValueIncrement = bidValueIncrement;
- this._lastBid = undefined;
- this._init = function(cb)
- {
- self._database.query("SELECT id FROM bids WHERE product = ? "
- +"ORDER BY centiTime DESC LIMIT 1",
- [self.getId()],
- function(results)
- {
- if(results.length == 0)
- {
- self._lastBid = null;
- self._init = undefined;
- if(cb)cb(self);
- return;
- }
- db.getBid(results[0].id, function(bid)
- {
- self._lastBid = bid;
- self._init = undefined;
- if(cb)cb(self);
- });
- });
- }
- /** @var An array with on-bid-placed-event listeners. */
- this._onBidPlacedEventListeners = [];
- /**
- *
- */
- this._triggerOnBidPlacedEventListeners = function(bid)
- {
- for(var i=0; i<self._onBidPlacedEventListeners.length; i++)
- self._onBidPlacedEventListeners[i](bid);
- }
- /**
- * Add a on-bid-placed-event-listener. The function will be called everytime
- * a bid is placed on any product. The callback will be given a
- * Bid-object which the bid was placed on as a first parameter.
- * @param cb The event-listener.
- */
- this.addOnBidPlacedEventListener = function(cb)
- {
- self._onBidPlacedEventListeners.add(cb);
- }
- this.removeOnBidPlacedEventListener = function(cb)
- {
- self._onBidPlacedEventListeners.remove(cb);
- }
- /**
- * Get the id of this product.
- * @type Number
- * @return the id
- */
- this.getId = function()
- {
- return self._id;
- }
- this.getTitle = function()
- {
- return self._title;
- }
- this.getThumbnail = function()
- {
- return self._thumbnail;
- }
- this.getValue = function()
- {
- return self._value;
- }
- /**
- * Get the time this Product will start.
- * @type Date
- * @return The time
- */
- this.getStartTime = function()
- {
- return self._time;
- }
- this.getLastBid = function()
- {
- return self._lastBid;
- }
- this.placeBid = function(time, user, cb)
- {
- var centiTime = (time.getTime() - self.getStartTime().getTime())/10;
- self._database.query(
- "INSERT INTO bids (product, centiTime, user) VALUES(?,?,?)",
- [self.getId(), centiTime, user.getId()],
- function(results)
- {
- db.getBid(results.insertId, function(bid)
- {
- if(self._lastBid)
- self._database.uncacheBid(self._lastBid);
- self._lastBid = bid;
- self._triggerOnBidPlacedEventListeners(bid);
- if(cb)cb(bid);
- });
- });
- }
- this.getBidDuration = function()
- {
- return Math.max(self._bidDurationMinimum,
- self._bidDurationStart
- - self._bidDurationDecrementAmount
- * Math.floor((new Date().getTime() - self._time.getTime())
- / (self._bidDurationDecrementInterval*1000)));
- };
- this.getBidTimeRemaining = function()
- {
- console.log("getBidDuration: "+self.getBidDuration());
- if(!self._lastBid)
- return self.getBidDuration();
- console.log("getBidTimeRemaining: "+(self.getBidDuration()
- - ( new Date().getTime()
- - self._lastBid.getTime().getTime())/1000));
- return Math.max(0, self.getBidDuration()
- - ( new Date().getTime()
- - self._lastBid.getTime().getTime())/1000);
- };
- this.getBidDurationMinimum = function()
- {
- return self._bidDurationMinimum;
- }
- this.getBidDurationDecrementInterval = function()
- {
- return self._bidDurationDecrementInterval;
- }
- this.getBidDurationDecrementAmount = function()
- {
- return self._bidDurationDecrementAmount;
- }
- this.getBidDurationStart = function()
- {
- return self._bidDurationStart;
- }
- this.getBidValueIncrement = function()
- {
- return self._bidValueIncrement;
- }
- this.getBidValue = function()
- {
- return self.getLastBid()?self.getLastBid().getValue():0;
- }
- this.toString = function()
- {
- return "[Product #"+self.getId()+" "
- +self.getTitle()+" "
- +self.getBidDuration()+"s \u20ac"
- +self.getBidValue().toCurrency()+"]";
- };
- this.toJSON = function()
- {
- var bid = self.getLastBid();
- return {
- id: self.getId(),
- title: self.getTitle(),
- time: self.getStartTime().getTime(),
- thumbnail: self.getThumbnail(),
- value: self.getValue(),
- bidDurationMinimum: self.getBidDurationMinimum(),
- bidDurationStart: self.getBidDurationStart(),
- bidDurationDecrementInterval: self.getBidDurationDecrementInterval(),
- bidDurationDecrementAmount: self.getBidDurationDecrementAmount(),
- bidValueIncrement: self.getBidValueIncrement(),
- lastBid: !bid?null:{
- id: bid.getId(),
- time: bid.getTime().getTime(),
- offset: bid.getOffset(),
- user: bid.getUser().toJSON()
- }
- };
- }
- }
- function Bid(db, id, product, time, user)
- {
- var self = this;
- this._database = db;
- this._id = id;
- this._product = product;
- this._time = time;
- this._user = user;
- this._offset = undefined;
- this._init = function(cb)
- {
- self._database.query("SELECT COUNT(1) AS count FROM bids WHERE "
- +"product = ? AND id < ?",
- [self._product.getId(), self.getId()],
- function(results)
- {
- self._offset = results[0].count+1;
- if(cb)cb(self);
- });
- }
- this.getId = function()
- {
- return self._id;
- }
- this.getOffset = function()
- {
- return self._offset;
- }
- /**
- * Get the time this Bid was placed.
- * @type Date
- * @return The time
- */
- this.getTime = function()
- {
- return self._time;
- }
- this.getProduct = function()
- {
- return self._product;
- }
- this.getUser = function()
- {
- return self._user;
- }
- this.getValue = function()
- {
- return self.getOffset() * self.getProduct().getBidValueIncrement();
- }
- this.toString = function()
- {
- return "[Bid #"+self.getId()+" n"
- +self.getOffset()+" \u20ac"
- +self.getValue().toCurrency()+"]";
- };
- this.toJSON = function()
- {
- return {
- id: self.getId(),
- time: self.getTime().getTime(),
- user: self.getUser().toJSON(),
- product: self.getProduct().toJSON(),
- offset: self.getOffset()
- };
- }
- }
- function ProductAutoBidBot(product, users)
- {
- var self = this;
- this._product = product;
- this._users = users;
- this._placeBidTimeout = null;
- var placeBidByRandomUser = function()
- {
- var user = self._users.getRandomElement();
- var time = self._product.getLastBid().getTime().getTime()
- + (self._product.getBidDuration()-1)*1000;
- console.log("AutoBid placed Bid on "+product+" by "+user);
- self._product.placeBid(new Date(time), user);
- }
- var bidPlacedListener = function()
- {
- var secs = self._product.getBidTimeRemaining();
- console.log("Scheduling AutoBid on "+product+" in "+secs);
- clearTimeout(self._placeBidTimeout);
- self._placeBidTimeout = setTimeout(placeBidByRandomUser, secs*1000);
- }
- this.start = function()
- {
- self._product.addOnBidPlacedEventListener(bidPlacedListener);
- bidPlacedListener();
- }
- this.stop = function()
- {
- clearTimeout(self._placeBidTimeout);
- self._product.removeOnBidPlacedEventListener(bidPlacedListener);
- }
- }
- function handleJSONResponse(response)
- {
- switch(response.getParam(0))
- {
- case "place-bid":
- var bidProduct = undefined;
- var bidUser = undefined;
- var productAndUserFetchedCallback = function()
- {
- if(bidProduct === undefined
- || bidUser === undefined)
- return;
- if(!bidUser||!bidProduct)
- {
- if(!bidUser)
- response.addError("Placing a bid requires a login.");
- if(!bidProduct)
- response.addError("Product does not exist.");
- response.end();
- return;
- }
- bidProduct.placeBid(response.getTime(), bidUser,
- function(bid)
- {
- response.set("bid", bid?bid.toJSON():null);
- response.end();
- });
- }
- db.getProduct(response.getParam(1), function(product)
- {
- bidProduct = product;
- productAndUserFetchedCallback();
- });
- response.validateLogin(function(user)
- {
- bidUser = user;
- productAndUserFetchedCallback();
- });
- break;
- case "next-bid-since":
- var sinceBidId = response.getParam(1);
- if(db.getLastBid()
- && db.getLastBid().getId() != sinceBidId)
- {
- db.getLastBidsPlacedAfterBidId(sinceBidId,
- function(bids)
- {
- var bidData = new Array(bids.length);
- for(var i=0; i<bidData.length; i++)
- bidData[i] = bids[i].toJSON();
- response.set("bids", bidData);
- response.end();
- });
- }
- else
- {
- var rawResponse = null;
- var onBidPlacedListener = function(bid)
- {
- db.removeOnBidPlacedEventListener(onBidPlacedListener);
- if(!rawResponse)
- {
- response.set("bids", [bid]);
- rawResponse = response.getRawResponse();
- }
- response.setRawResponse(rawResponse);
- response.end();
- };
- db.addOnBidPlacedEventListener(onBidPlacedListener);
- }
- break;
- case "active-products":
- db.getProductsStartingBetween(new Date(0), new Date(),
- function(products)
- {
- var productData = new Array(products.length);
- for(var i=0; i<productData.length; i++)
- productData[i] = products[i].toJSON();
- response.set("activeProducts", productData);
- response.end();
- });
- break;
- case "product-info":
- db.getProduct(response.getParam(1), function(product)
- {
- response.set("product", product?product.toJSON():null);
- response.end();
- });
- break;
- case "bid-info":
- db.getBid(response.getParam(1), function(bid)
- {
- response.set("bid", bid?bid.toJSON():null);
- response.end();
- });
- break;
- case "user-info":
- db.getUser(response.getParam(1), function(user)
- {
- response.set("user", user?user.toJSON():null);
- response.end();
- });
- break;
- case "time":
- response.set("time", response.getTime().getTime());
- response.end();
- break;
- default:
- response.addError("Unknown Command");
- response.end();
- }
- }
- var client = mysql.Client();
- client.host = settings.sqlHost;
- client.user = settings.sqlUser;
- client.password = settings.sqlPass;
- client.database = settings.sqlDatabase;
- client.connect();
- var db = new Database(client);
- (function()
- {
- var botUsers = new Array();
- var botsLoadedCallbacks = new Array();
- db.addProductLoadedEventListener(function(product)
- {
- var botUsersReadyCallback = function()
- {
- var bot = new ProductAutoBidBot(product, botUsers);
- bot.start();
- }
- if(botsLoadedCallbacks !== null)
- botsLoadedCallbacks.push(botUsersReadyCallback);
- else
- botUsersReadyCallback();
- });
- db._init(function(db)
- {
- var botUserIds = [11, 12];
- var botsLoadedCallback = function()
- {
- if(botUsers.length < botUserIds.length)
- return;
- for(var i=0; i<botsLoadedCallbacks.length; i++)
- botsLoadedCallbacks[i]();
- }
- for(var i=0; i<botUserIds.length; i++)
- db.getUser(botUserIds[i], function(user)
- {
- botUsers.push(user);
- botsLoadedCallback();
- });
- http.createServer(function(request, response)
- {
- var jsonresponse = new JSONResponse(db, request, response);
- handleJSONResponse(jsonresponse);
- }).listen(settings.httpPort);
- });
- })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement