<commit_before>(function () {
	"use strict";

	module.exports = function ($rootScope, $filter, $http, $timeout, $interval, $q, ngToast, bootbox, localStorageService, $, config, uuid, webNotification, iosocket, utils) {

		var _this = this;

		var API = {
			GETINFO: "/api/lnd/getinfo",
			GETNODEINFO: "/api/lnd/getnodeinfo",
			GETNETWORKINFO: "/api/lnd/getnetworkinfo",
			WALLETBALANCE: "/api/lnd/walletbalance",
			CHANNELBALANCE: "/api/lnd/channelbalance",
			LISTPEERS: "/api/lnd/listpeers",
			LISTCHANNELS: "/api/lnd/listchannels",
			PENDINGCHANNELS: "/api/lnd/pendingchannels",
			LISTPAYMENTS: "/api/lnd/listpayments",
			LISTINVOICES: "/api/lnd/listinvoices",
			FORWARDINGHISTORY: "/api/lnd/forwardinghistory",
			CONNECTPEER: "/api/lnd/connectpeer",
			DISCONNECTPEER: "/api/lnd/disconnectpeer",
			ADDINVOICE: "/api/lnd/addinvoice",
			SENDCOINS: "/api/lnd/sendcoins",
			SENDPAYMENT: "/api/lnd/sendpayment",
			DECODEPAYREQ: "/api/lnd/decodepayreq",
			QUERYROUTE: "/api/lnd/queryroute",
			NEWADDRESS: "/api/lnd/newaddress",
			RENDERGRAPH: "/api/lnd/rendergraph",
			SIGNMESSAGE: "/api/lnd/signmessage",
			VERIFYMESSAGE: "/api/lnd/verifymessage"
		};

		var BLOCKCHAIN_INFO_TICKER_URL = "https://blockchain.info/ticker";
		var PRICE_CACHE_MAXAGE = 30 * 1000;

		this.restrictedUser = false;

		var infoCache = null;
		var peersCache = null;
		var channelsCache = null;
		var knownPeersCache = null;
		var configCache = null;
		var addressesCache = null;
		var channelsFetchers = null;
		var pricesCache = null;
		var pricesFetchers = null;
		var wsRequestListeners = {};

		var endPoint = utils.getUrlParameterByName("endpoint") || window.serverRootPath; // endpoint parameter -> LND Chrome Extension, window.serverRootPath -> Electron

		var serverUrl = function (path) {
			return endPoint ? endPoint + path : path;
		};

		this.getEndPoint = function () {
			return endPoint ? endPoint : window.location.origin;
		};

		var isSecure = function () {
			return endPoint ? endPoint.toLowerCase().startsWith("https:") : location.protocol.startsWith("https");
		};

		var socket = iosocket.connect(serverUrl("/"), { secure: isSecure() });

		socket.on(config.events.INVOICE_WS, function (data) {
			console.log("Invoice received:", data);
			_this.notify(config.notif.SUCCESS, "Payment received: " + data.value + ", " + data.memo);
			$rootScope.$broadcast(config.events.INVOICE_REFRESH, data);
		});

		socket.on(config.events.HELLO_WS, function (data) {
			console.log("Hello event received:", data);
			_this.restrictedUser = data.limitUser;
			onConfigUpdate();
			var helloMsg = ((data && data.remoteAddress) ? data.remoteAddress + " s" : "S") + "ucessfully connected!";
			_this.notify(config.notif.SUCCESS, helloMsg);
		});

		var resolvedPromise = typeof Promise == "undefined" ? null : Promise.resolve();
		this.nextTick = resolvedPromise ? function (fn) { resolvedPromise.then(fn); } : function (fn) { setTimeout(fn); };

		var logLines = 0;
		socket.on(config.events.LOG_WS, function (message) {
			console.log("Log message:", message);
			if (message.data) {
				var logNotifyPattern = _this.getConfigValue(
					config.keys.LOG_NOTIFY_PATTERN, config.defaults.LOG_NOTIFY_PATTERN);
				var logPatternRE = null;
				if (logNotifyPattern) {
					logPatternRE = new RegExp(logNotifyPattern);
				}
				var index = -1;
				var previndex = 0;
				while ((index = message.data.indexOf("\n", index + 1)) > -1) {
					if (logPatternRE) {
						var logLine = message.data.substr(previndex, (index - previndex));
						if (logLine.match(logPatternRE)) {
							_this.notify(config.notif.WARNING, logLine);
						}
					}
					previndex = index + 1;
					logLines++;
				}
				var $tailObj = $("#tail");
				var logs = $tailObj.html();
				index = -1;
				var maxLogBuffer = _this.getConfigValue(
					config.keys.MAX_LOG_BUFFER, config.defaults.MAX_LOG_BUFFER);
				while (logLines > maxLogBuffer) {
					index = logs.indexOf("\n", index + 1);
					logLines--;
				}
				logs = logs.substring(index + 1);
				$tailObj.html(logs + message.data);
				_this.nextTick(function () {
					$tailObj.scrollTop($tailObj[0].scrollHeight);
				});
			}
		});

		socket.on(config.events.OPENCHANNEL_WS, function (response) {
			console.log("OpenChannel WS event", response);
			$timeout(function () {
				if (response.evt && response.rid) { // valid event
					if (wsRequestListenersFilter(response)) {
						if ((response.evt === "error") && response.data.error) {
							var errMsg = response.data.error;
							_this.notify(config.notif.WARNING, errMsg);
						} else if ((response.evt === "status")) {
							var statusMsg = response.data.details;
							_this.notify(config.notif.INFO, statusMsg);
						} else if ((response.evt === "data") && response.data.update) {
							var dataMsg;
							var update = response.data.update; // TODO
							if (update === "chan_pending") {
								var txId = response.data.chan_pending.txid;
								dataMsg = "Channel opening pending... (txid = " + utils.buffer2hexa(txId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else if (update === "confirmation") {
								var blockHeight = response.data.confirmation.block_height;
								var numConfsLeft = response.data.confirmation.num_confs_left;
								dataMsg = "Channel opening, " + numConfsLeft + " confirmations left... (block height " + blockHeight + ")";
							} else if (update === "chan_open") {
								var fundingTxId = response.data.chan_open.channel_point.funding_txid;
								dataMsg = "Channel is opened... (funding txid = " + utils.buffer2hexa(fundingTxId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else {
								dataMsg = update;
							}
							_this.notify(config.notif.INFO, dataMsg);
						}
					}
				}
			});
		});

		socket.on(config.events.CLOSECHANNEL_WS, function (response) {
			console.log("CloseChannel WS event", response);
			$timeout(function () {
				if (response.evt && response.rid) { // valid event
					if (wsRequestListenersFilter(response)) {
						if ((response.evt === "error") && response.data.error) {
							var errMsg = response.data.error;
							_this.notify(config.notif.WARNING, errMsg);
						} else if ((response.evt === "status")) {
							var statusMsg = response.data.details;
							_this.notify(config.notif.INFO, statusMsg);
						} else if ((response.evt === "data") && response.data.update) {
							var dataMsg;
							var update = response.data.update; // TODO
							if (update === "close_pending") {
								var txId = response.data.close_pending.txid;
								dataMsg = "Channel close pending... (txid = " + utils.buffer2hexa(txId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else if (update === "chan_close") {
								var closingTxId = response.data.chan_close.closing_txid;
								dataMsg = "Channel has been closed... (closing txid = " + utils.buffer2hexa(closingTxId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else {
								dataMsg = update;
							}
							_this.notify(config.notif.INFO, dataMsg);
						}
					}
				}
			});
		});

		var wsRequestListenersFilter = function (response) {
			if (wsRequestListeners.hasOwnProperty(response.rid)) {
				return wsRequestListeners[response.rid].callback(response);
			} else {
				return true;
			}
		};

		this.registerWSRequestListener = function (requestId, callback, expires) {
			var deferred = $q.defer();
			expires = expires || new Date().getTime() + 5 * 60 * 1000; // defaults to five minutes
			wsRequestListeners[requestId] = {
				expires: expires,
				callback: callback,
				deferred: deferred
			};
			return deferred.promise;
		};

		this.unregisterWSRequestListener = function (requestId) {
			if (wsRequestListeners.hasOwnProperty(requestId)) {
				var requestListener = wsRequestListeners[requestId];
				requestListener.deferred.resolve();
				delete wsRequestListeners[requestId];
			}
		};

		var wsListenersCleaner = $interval(function () {
			var count = 0;
			var now = new Date().getTime();
			for (var requestId in wsRequestListeners) {
				_this.unregisterWSRequestListener();
				if (wsRequestListeners.hasOwnProperty(requestId)) {
					var requestListener = wsRequestListeners[requestId];
					if (requestListener.expires < now) {
						_this.unregisterWSRequestListener(requestId);
					}
				}
			}
			console.log(count + " websocket listeners cleaned");
		}, 60 * 1000); // every 60 seconds

		var notifLines = 0;
		this.notify = function (type, message) {
			console.log("Notification (" + type + ") :", message);
			if (message) {
				$timeout(function () {
					if (type === config.notif.INFO) {
						ngToast.info({
							content: message
						});
					} else if (type === config.notif.SUCCESS) {
						ngToast.success({
							content: message
						});
					} else if (type === config.notif.WARNING) {
						ngToast.warning({
							content: message
						});
					}
					webNotification.showNotification("Lnd Web Client notification", {
						body: message,
						icon: "favicon.ico",
						onClick: function (event) {
							console.log("Web notification clicked");
							event.currentTarget.close();
						},
						autoClose: 4000 // 4 seconds
					}, function onShow(error, hide) {
						if (error) {
							_this.alert("Unable to show web notification: " + error.message);
						} else {
							console.log("Web notification shown");
						}
					});
				});
				var index = -1;
				while ((index = message.indexOf("\n", index + 1)) > -1) {
					notifLines++;
				}
				var $notifObj = $("#notifications");
				var notifHtml = $notifObj.html();
				index = -1;
				var maxLogBuffer = _this.getConfigValue(
					config.keys.MAX_NOTIF_BUFFER, config.defaults.MAX_NOTIF_BUFFER);
				while (notifLines > maxLogBuffer) {
					index = notifHtml.indexOf("\n", index + 1);
					notifLines--;
				}
				notifHtml = notifHtml.substring(index + 1);
				var now = $filter("date")(new Date(), "yyyy-MM-dd HH:mm:ss Z");
				$notifObj.html(notifHtml + now + " - " + type + " - " + message + "\n");
				_this.nextTick(function () {
					$notifObj.scrollTop($notifObj[0].scrollHeight);
				});
			}
		};

		this.alert = function (message) {
			if (message && message.length > 0) {
				bootbox.alert(message);
			}
		};

		var fetchConfig = function () {
			configCache = localStorageService.get("config"); // update cache
			if (!configCache) { configCache = {}; }
			return configCache;
		};

		var writeConfig = function (config) {
			localStorageService.set("config", config);
			configCache = config; // update cache
			onConfigUpdate();
		};

		var fetchAddresses = function () {
			addressesCache = localStorageService.get("addresses"); // update cache
			if (!addressesCache) { addressesCache = {}; }
			return addressesCache;
		};

		var writeAddresses = function (addresses) {
			localStorageService.set("addresses", addresses);
			addressesCache = addresses; // update cache
		};

		var updateKnownPeers = function (peers, peersFromLnd) {
			var knownPeers = fetchKnownPeers();
			for (var i = 0; i < peers.length; i++) {
				var peer = peers[i];
				var knownPeer = knownPeers[peer.pub_key] || {};
				var knownPeerAddress = knownPeer.address;
				knownPeer = Object.assign(knownPeer, peer);
				if (peersFromLnd) {
					if (knownPeer.custom_alias && !peer.custom_alias) {
						// inject custom alias into lnd peer for easy display use
						peer.custom_alias = knownPeer.custom_alias;
						// inject color into lnd peer for easy display use
						peer.color = knownPeer.color;
					}
					if (knownPeerAddress && (knownPeerAddress != peer.address)) {
						var peerHostPort = peer.address.split(":");
						var knownPeerHostPort = knownPeer.address.split(":");
						knownPeer.address = peerHostPort[0] + ":" + knownPeerHostPort[1]; // keep overriden port
					}
					knownPeer.lastseen = new Date().getTime();<commit_msg>Known peer custom port reset on refresh bug fix<commit_after>(function () {
	"use strict";

	module.exports = function ($rootScope, $filter, $http, $timeout, $interval, $q, ngToast, bootbox, localStorageService, $, config, uuid, webNotification, iosocket, utils) {

		var _this = this;

		var API = {
			GETINFO: "/api/lnd/getinfo",
			GETNODEINFO: "/api/lnd/getnodeinfo",
			GETNETWORKINFO: "/api/lnd/getnetworkinfo",
			WALLETBALANCE: "/api/lnd/walletbalance",
			CHANNELBALANCE: "/api/lnd/channelbalance",
			LISTPEERS: "/api/lnd/listpeers",
			LISTCHANNELS: "/api/lnd/listchannels",
			PENDINGCHANNELS: "/api/lnd/pendingchannels",
			LISTPAYMENTS: "/api/lnd/listpayments",
			LISTINVOICES: "/api/lnd/listinvoices",
			FORWARDINGHISTORY: "/api/lnd/forwardinghistory",
			CONNECTPEER: "/api/lnd/connectpeer",
			DISCONNECTPEER: "/api/lnd/disconnectpeer",
			ADDINVOICE: "/api/lnd/addinvoice",
			SENDCOINS: "/api/lnd/sendcoins",
			SENDPAYMENT: "/api/lnd/sendpayment",
			DECODEPAYREQ: "/api/lnd/decodepayreq",
			QUERYROUTE: "/api/lnd/queryroute",
			NEWADDRESS: "/api/lnd/newaddress",
			RENDERGRAPH: "/api/lnd/rendergraph",
			SIGNMESSAGE: "/api/lnd/signmessage",
			VERIFYMESSAGE: "/api/lnd/verifymessage"
		};

		var BLOCKCHAIN_INFO_TICKER_URL = "https://blockchain.info/ticker";
		var PRICE_CACHE_MAXAGE = 30 * 1000;

		this.restrictedUser = false;

		var infoCache = null;
		var peersCache = null;
		var channelsCache = null;
		var knownPeersCache = null;
		var configCache = null;
		var addressesCache = null;
		var channelsFetchers = null;
		var pricesCache = null;
		var pricesFetchers = null;
		var wsRequestListeners = {};

		var endPoint = utils.getUrlParameterByName("endpoint") || window.serverRootPath; // endpoint parameter -> LND Chrome Extension, window.serverRootPath -> Electron

		var serverUrl = function (path) {
			return endPoint ? endPoint + path : path;
		};

		this.getEndPoint = function () {
			return endPoint ? endPoint : window.location.origin;
		};

		var isSecure = function () {
			return endPoint ? endPoint.toLowerCase().startsWith("https:") : location.protocol.startsWith("https");
		};

		var socket = iosocket.connect(serverUrl("/"), { secure: isSecure() });

		socket.on(config.events.INVOICE_WS, function (data) {
			console.log("Invoice received:", data);
			_this.notify(config.notif.SUCCESS, "Payment received: " + data.value + ", " + data.memo);
			$rootScope.$broadcast(config.events.INVOICE_REFRESH, data);
		});

		socket.on(config.events.HELLO_WS, function (data) {
			console.log("Hello event received:", data);
			_this.restrictedUser = data.limitUser;
			onConfigUpdate();
			var helloMsg = ((data && data.remoteAddress) ? data.remoteAddress + " s" : "S") + "ucessfully connected!";
			_this.notify(config.notif.SUCCESS, helloMsg);
		});

		var resolvedPromise = typeof Promise == "undefined" ? null : Promise.resolve();
		this.nextTick = resolvedPromise ? function (fn) { resolvedPromise.then(fn); } : function (fn) { setTimeout(fn); };

		var logLines = 0;
		socket.on(config.events.LOG_WS, function (message) {
			console.log("Log message:", message);
			if (message.data) {
				var logNotifyPattern = _this.getConfigValue(
					config.keys.LOG_NOTIFY_PATTERN, config.defaults.LOG_NOTIFY_PATTERN);
				var logPatternRE = null;
				if (logNotifyPattern) {
					logPatternRE = new RegExp(logNotifyPattern);
				}
				var index = -1;
				var previndex = 0;
				while ((index = message.data.indexOf("\n", index + 1)) > -1) {
					if (logPatternRE) {
						var logLine = message.data.substr(previndex, (index - previndex));
						if (logLine.match(logPatternRE)) {
							_this.notify(config.notif.WARNING, logLine);
						}
					}
					previndex = index + 1;
					logLines++;
				}
				var $tailObj = $("#tail");
				var logs = $tailObj.html();
				index = -1;
				var maxLogBuffer = _this.getConfigValue(
					config.keys.MAX_LOG_BUFFER, config.defaults.MAX_LOG_BUFFER);
				while (logLines > maxLogBuffer) {
					index = logs.indexOf("\n", index + 1);
					logLines--;
				}
				logs = logs.substring(index + 1);
				$tailObj.html(logs + message.data);
				_this.nextTick(function () {
					$tailObj.scrollTop($tailObj[0].scrollHeight);
				});
			}
		});

		socket.on(config.events.OPENCHANNEL_WS, function (response) {
			console.log("OpenChannel WS event", response);
			$timeout(function () {
				if (response.evt && response.rid) { // valid event
					if (wsRequestListenersFilter(response)) {
						if ((response.evt === "error") && response.data.error) {
							var errMsg = response.data.error;
							_this.notify(config.notif.WARNING, errMsg);
						} else if ((response.evt === "status")) {
							var statusMsg = response.data.details;
							_this.notify(config.notif.INFO, statusMsg);
						} else if ((response.evt === "data") && response.data.update) {
							var dataMsg;
							var update = response.data.update; // TODO
							if (update === "chan_pending") {
								var txId = response.data.chan_pending.txid;
								dataMsg = "Channel opening pending... (txid = " + utils.buffer2hexa(txId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else if (update === "confirmation") {
								var blockHeight = response.data.confirmation.block_height;
								var numConfsLeft = response.data.confirmation.num_confs_left;
								dataMsg = "Channel opening, " + numConfsLeft + " confirmations left... (block height " + blockHeight + ")";
							} else if (update === "chan_open") {
								var fundingTxId = response.data.chan_open.channel_point.funding_txid;
								dataMsg = "Channel is opened... (funding txid = " + utils.buffer2hexa(fundingTxId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else {
								dataMsg = update;
							}
							_this.notify(config.notif.INFO, dataMsg);
						}
					}
				}
			});
		});

		socket.on(config.events.CLOSECHANNEL_WS, function (response) {
			console.log("CloseChannel WS event", response);
			$timeout(function () {
				if (response.evt && response.rid) { // valid event
					if (wsRequestListenersFilter(response)) {
						if ((response.evt === "error") && response.data.error) {
							var errMsg = response.data.error;
							_this.notify(config.notif.WARNING, errMsg);
						} else if ((response.evt === "status")) {
							var statusMsg = response.data.details;
							_this.notify(config.notif.INFO, statusMsg);
						} else if ((response.evt === "data") && response.data.update) {
							var dataMsg;
							var update = response.data.update; // TODO
							if (update === "close_pending") {
								var txId = response.data.close_pending.txid;
								dataMsg = "Channel close pending... (txid = " + utils.buffer2hexa(txId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else if (update === "chan_close") {
								var closingTxId = response.data.chan_close.closing_txid;
								dataMsg = "Channel has been closed... (closing txid = " + utils.buffer2hexa(closingTxId, true) + ")";
								$rootScope.$broadcast(config.events.CHANNEL_REFRESH, response);
							} else {
								dataMsg = update;
							}
							_this.notify(config.notif.INFO, dataMsg);
						}
					}
				}
			});
		});

		var wsRequestListenersFilter = function (response) {
			if (wsRequestListeners.hasOwnProperty(response.rid)) {
				return wsRequestListeners[response.rid].callback(response);
			} else {
				return true;
			}
		};

		this.registerWSRequestListener = function (requestId, callback, expires) {
			var deferred = $q.defer();
			expires = expires || new Date().getTime() + 5 * 60 * 1000; // defaults to five minutes
			wsRequestListeners[requestId] = {
				expires: expires,
				callback: callback,
				deferred: deferred
			};
			return deferred.promise;
		};

		this.unregisterWSRequestListener = function (requestId) {
			if (wsRequestListeners.hasOwnProperty(requestId)) {
				var requestListener = wsRequestListeners[requestId];
				requestListener.deferred.resolve();
				delete wsRequestListeners[requestId];
			}
		};

		var wsListenersCleaner = $interval(function () {
			var count = 0;
			var now = new Date().getTime();
			for (var requestId in wsRequestListeners) {
				_this.unregisterWSRequestListener();
				if (wsRequestListeners.hasOwnProperty(requestId)) {
					var requestListener = wsRequestListeners[requestId];
					if (requestListener.expires < now) {
						_this.unregisterWSRequestListener(requestId);
					}
				}
			}
			console.log(count + " websocket listeners cleaned");
		}, 60 * 1000); // every 60 seconds

		var notifLines = 0;
		this.notify = function (type, message) {
			console.log("Notification (" + type + ") :", message);
			if (message) {
				$timeout(function () {
					if (type === config.notif.INFO) {
						ngToast.info({
							content: message
						});
					} else if (type === config.notif.SUCCESS) {
						ngToast.success({
							content: message
						});
					} else if (type === config.notif.WARNING) {
						ngToast.warning({
							content: message
						});
					}
					webNotification.showNotification("Lnd Web Client notification", {
						body: message,
						icon: "favicon.ico",
						onClick: function (event) {
							console.log("Web notification clicked");
							event.currentTarget.close();
						},
						autoClose: 4000 // 4 seconds
					}, function onShow(error, hide) {
						if (error) {
							_this.alert("Unable to show web notification: " + error.message);
						} else {
							console.log("Web notification shown");
						}
					});
				});
				var index = -1;
				while ((index = message.indexOf("\n", index + 1)) > -1) {
					notifLines++;
				}
				var $notifObj = $("#notifications");
				var notifHtml = $notifObj.html();
				index = -1;
				var maxLogBuffer = _this.getConfigValue(
					config.keys.MAX_NOTIF_BUFFER, config.defaults.MAX_NOTIF_BUFFER);
				while (notifLines > maxLogBuffer) {
					index = notifHtml.indexOf("\n", index + 1);
					notifLines--;
				}
				notifHtml = notifHtml.substring(index + 1);
				var now = $filter("date")(new Date(), "yyyy-MM-dd HH:mm:ss Z");
				$notifObj.html(notifHtml + now + " - " + type + " - " + message + "\n");
				_this.nextTick(function () {
					$notifObj.scrollTop($notifObj[0].scrollHeight);
				});
			}
		};

		this.alert = function (message) {
			if (message && message.length > 0) {
				bootbox.alert(message);
			}
		};

		var fetchConfig = function () {
			configCache = localStorageService.get("config"); // update cache
			if (!configCache) { configCache = {}; }
			return configCache;
		};

		var writeConfig = function (config) {
			localStorageService.set("config", config);
			configCache = config; // update cache
			onConfigUpdate();
		};

		var fetchAddresses = function () {
			addressesCache = localStorageService.get("addresses"); // update cache
			if (!addressesCache) { addressesCache = {}; }
			return addressesCache;
		};

		var writeAddresses = function (addresses) {
			localStorageService.set("addresses", addresses);
			addressesCache = addresses; // update cache
		};

		var updateKnownPeers = function (peers, peersFromLnd) {
			var knownPeers = fetchKnownPeers();
			for (var i = 0; i < peers.length; i++) {
				var peer = peers[i];
				var knownPeer = knownPeers[peer.pub_key] || {};
				var knownPeerAddress = knownPeer.address;
				knownPeer = Object.assign(knownPeer, peer);
				if (peersFromLnd) {
					if (knownPeer.custom_alias && !peer.custom_alias) {
						// inject custom alias into lnd peer for easy display use
						peer.custom_alias = knownPeer.custom_alias;
						// inject color into lnd peer for easy display use
						peer.color = knownPeer.color;
					}
					if (knownPeerAddress && (knownPeerAddress != peer.address)) {
						var peerHostPort = peer.address.split(":");
						var knownPeerHostPort = knownPeerAddress.split(":");
						knownPeer.address = peerHostPort[0] + ":" + knownPeerHostPort[1]; // keep overriden port
					}
					knownPeer.lastseen = new Date().getTime();
{'fraction_non_alphanumeric': 0.1436185717260046, 'fraction_numerical': 0.004413907974182802, 'mean_word_length': 3.159334949774853, 'pattern_counts': {'":': 6, '<': 7, '<?xml version=': 0, '>': 17, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7565791', 'n_tokens_mistral': 9649, 'n_tokens_neox': 7575, 'n_words': 1567}