3a2f679201
-A number of functions have been rewritten to be more optimized and faster: calculate_total, is_unique, convert_to_satoshi, get_input_addresses, processVoutAddresses, prepare_vout, prepare_vin -Txes are now written to database via bulk writes which helps improve the sync speed and also controls memory usage with batching to write data once a certain threshold is reached -update_address function changed to update_addresses since it now bulk writes the addresses in batches to improve sync speed and also controls memory usage with batching to write data once a certain threshold is reached -The syncLoop function has been completely removed from the project and replaced with async library loops or even normal "for" loops in some cases which greatly improves sync speeds over large batches of data -Fixed an issue with the flattened count of txes that is saved to the coinstats collection which could save incorrectly when using more than 1 thread -Fixed an issue with the block sync which caused an unwanted delay when syncing less blocks than the amount of threads used to sync the data -Fixed an issue with vout data processing that could sometimes populate data out of order -Added a new sync.batch_size setting used to determine how many records (txes, addresses, addresstxes) should be saved in a single database transaction -Added a new wait_for_bulk_database_save setting used to increase the block sync speed at the cost of not returning any error msgs for data that failed to save -get_input_addresses function no longer returns in the exports section of the explorer.js file since it is only referenced in that file -Updated explorerspec tests to use the newest function changes for any tests that needed to be updated Special thanks to Karzo from Pepecoin for help with the bulkwrite code changes!
1259 lines
58 KiB
JavaScript
1259 lines
58 KiB
JavaScript
var express = require('express'),
|
|
path = require('path'),
|
|
nodeapi = require('./lib/nodeapi'),
|
|
favicon = require('serve-favicon'),
|
|
logger = require('morgan'),
|
|
cookieParser = require('cookie-parser'),
|
|
bodyParser = require('body-parser'),
|
|
settings = require('./lib/settings'),
|
|
routes = require('./routes/index'),
|
|
lib = require('./lib/explorer'),
|
|
db = require('./lib/database'),
|
|
package_metadata = require('./package.json');
|
|
var app = express();
|
|
var apiAccessList = [];
|
|
var viewPaths = [path.join(__dirname, 'views')]
|
|
var pluginRoutes = [];
|
|
const { exec } = require('child_process');
|
|
|
|
// pass wallet rpc connection info to nodeapi
|
|
nodeapi.setWalletDetails(settings.wallet);
|
|
// dynamically build the nodeapi cmd access list by adding all non-blockchain-specific api cmds that have a value
|
|
Object.keys(settings.api_cmds).forEach(function(key, index, map) {
|
|
if (key != 'use_rpc' && key != 'rpc_concurrent_tasks' && settings.api_cmds[key] != null && settings.api_cmds[key] != '')
|
|
apiAccessList.push(key);
|
|
});
|
|
// dynamically find and add additional blockchain_specific api cmds
|
|
Object.keys(settings.blockchain_specific).forEach(function(key, index, map) {
|
|
// check if this feature is enabled and has api cmds
|
|
if (settings.blockchain_specific[key].enabled == true && Object.keys(settings.blockchain_specific[key]).indexOf('api_cmds') > -1) {
|
|
// add all blockchain specific api cmds that have a value
|
|
Object.keys(settings.blockchain_specific[key]['api_cmds']).forEach(function(key2, index, map) {
|
|
if (settings.blockchain_specific[key]['api_cmds'][key2] != null && settings.blockchain_specific[key]['api_cmds'][key2] != '')
|
|
apiAccessList.push(key2);
|
|
});
|
|
}
|
|
});
|
|
|
|
// whitelist the cmds in the nodeapi access list
|
|
nodeapi.setAccess('only', apiAccessList);
|
|
|
|
// determine if http traffic should be forwarded to https
|
|
if (settings.webserver.tls.enabled == true && settings.webserver.tls.always_redirect == true) {
|
|
app.use(function(req, res, next) {
|
|
if (req.secure) {
|
|
// continue without redirecting
|
|
next();
|
|
} else {
|
|
// add webserver port to the host value if it does not already exist
|
|
const host = req.headers.host + (req.headers.host.indexOf(':') > -1 ? '' : ':' + settings.webserver.port.toString());
|
|
|
|
// redirect to the correct https page
|
|
res.redirect(301, 'https://' + host.replace(':' + settings.webserver.port.toString(), (settings.webserver.tls.port != 443 ? ':' + settings.webserver.tls.port.toString() : '')) + req.url);
|
|
}
|
|
});
|
|
}
|
|
|
|
// determine if cors should be enabled
|
|
if (settings.webserver.cors.enabled == true) {
|
|
app.use(function(req, res, next) {
|
|
res.header("Access-Control-Allow-Origin", settings.webserver.cors.corsorigin);
|
|
res.header('Access-Control-Allow-Methods', 'DELETE, PUT, GET, POST');
|
|
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
|
next();
|
|
});
|
|
}
|
|
|
|
// loop through all plugins defined in the settings
|
|
settings.plugins.allowed_plugins.forEach(function (plugin) {
|
|
// check if this plugin is enabled
|
|
if (plugin.enabled) {
|
|
const pluginName = (plugin.plugin_name == null ? '' : plugin.plugin_name);
|
|
|
|
// check if the plugin exists in the plugins directory
|
|
if (db.fs.existsSync(`./plugins/${pluginName}`)) {
|
|
// check if the plugin's local_plugin_settings file exists
|
|
if (db.fs.existsSync(`./plugins/${pluginName}/lib/local_plugin_settings.js`)) {
|
|
// load the local_plugin_settings.js file from the plugin
|
|
let localPluginSettings = require(`./plugins/${pluginName}/lib/local_plugin_settings`);
|
|
|
|
// loop through all local plugin settings
|
|
Object.keys(localPluginSettings).forEach(function(key, index, map) {
|
|
// check if this is a known setting type that should be brought into the main settings
|
|
if (key.endsWith('_page') && typeof localPluginSettings[key] === 'object' && localPluginSettings[key]['enabled'] == true) {
|
|
// this is a page setting
|
|
// add the page_id to the page setting
|
|
localPluginSettings[key].page_id = key;
|
|
|
|
// add the menu item title to the page setting
|
|
localPluginSettings[key].menu_title = localPluginSettings['localization'][`${key}_menu_title`];
|
|
|
|
// check if there is already a page for this plugin
|
|
if (plugin.pages == null) {
|
|
// initialize the pages array
|
|
plugin.pages = [];
|
|
}
|
|
|
|
// add this page setting to the main plugin data
|
|
plugin['pages'].push(localPluginSettings[key]);
|
|
} else if (key == 'public_apis') {
|
|
// this is a collection of new apis
|
|
// check if there is an ext section
|
|
if (localPluginSettings[key]['ext'] != null) {
|
|
// loop through all ext apis for this plugin
|
|
Object.keys(localPluginSettings[key]['ext']).forEach(function(extKey, extIndex, extMap) {
|
|
// add the name of the api into the object
|
|
localPluginSettings[key]['ext'][extKey]['api_name'] = extKey;
|
|
|
|
// loop through all parameters for this api and replace them in the description string if applicable
|
|
for (let p = 0; p < localPluginSettings[key]['ext'][extKey]['api_parameters'].length; p++)
|
|
localPluginSettings['localization'][`${extKey}_description`] = localPluginSettings['localization'][`${extKey}_description`].replace(new RegExp(`\\{${(p + 1)}}`, 'g'), localPluginSettings[key]['ext'][extKey]['api_parameters'][p]['parameter_name']);
|
|
|
|
// add the localized api description into the object
|
|
localPluginSettings[key]['ext'][extKey]['api_desc'] = localPluginSettings['localization'][`${extKey}_description`];
|
|
});
|
|
}
|
|
|
|
// copy the entire public_apis section from the plugin into the main settings
|
|
plugin.public_apis = localPluginSettings[key];
|
|
}
|
|
});
|
|
}
|
|
|
|
// check if the plugin's routes/index.js file exists
|
|
if (db.fs.existsSync(`./plugins/${pluginName}/routes/index.js`)) {
|
|
// get the plugin routes and save them to an array
|
|
pluginRoutes.push(require(`./plugins/${pluginName}/routes/index`));
|
|
|
|
// check if the plugin has a views directory
|
|
if (db.fs.existsSync(`./plugins/${pluginName}/views`)) {
|
|
// get the list of files in the views directory
|
|
const files = db.fs.readdirSync(`./plugins/${pluginName}/views`);
|
|
|
|
// filter the list of files to check if any have the .pug extension
|
|
const pugFiles = files.filter(file => path.extname(file) === '.pug');
|
|
|
|
// check if the plugin has 1 or more views
|
|
if (pugFiles.length > 0) {
|
|
// add this plugins view path to the list of view paths
|
|
viewPaths.push(path.resolve(`./plugins/${pluginName}/views`));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// view engine setup
|
|
app.set('views', viewPaths);
|
|
app.set('view engine', 'pug');
|
|
|
|
var default_favicon = '';
|
|
|
|
// loop through the favicons
|
|
Object.keys(settings.shared_pages.favicons).forEach(function(key, index, map) {
|
|
// remove the public directory from the path if exists
|
|
if (settings.shared_pages.favicons[key] != null && settings.shared_pages.favicons[key].indexOf('public/') > -1)
|
|
settings.shared_pages.favicons[key] = settings.shared_pages.favicons[key].replace(/public\//g, '');
|
|
|
|
// check if the favicon file exists
|
|
if (!db.fs.existsSync(path.join('./public', settings.shared_pages.favicons[key])))
|
|
settings.shared_pages.favicons[key] = '';
|
|
else if (default_favicon == '')
|
|
default_favicon = settings.shared_pages.favicons[key];
|
|
});
|
|
|
|
if (default_favicon != '')
|
|
app.use(favicon(path.join('./public', default_favicon)));
|
|
|
|
app.use(logger('dev'));
|
|
app.use(bodyParser.json());
|
|
app.use(bodyParser.urlencoded({ extended: true }));
|
|
app.use(cookieParser());
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
// routes
|
|
app.use('/api', nodeapi.app);
|
|
app.use('/', routes);
|
|
|
|
// loop through all plugin routes and add them to the app
|
|
pluginRoutes.forEach(function (r) {
|
|
app.use('/', r);
|
|
});
|
|
|
|
// post method to claim an address using verifymessage functionality
|
|
app.post('/claim', function(req, res) {
|
|
// validate captcha if applicable
|
|
validate_captcha(settings.claim_address_page.enable_captcha, req.body, function(captcha_error) {
|
|
// check if there was a problem with captcha
|
|
if (captcha_error) {
|
|
// show the captcha error
|
|
res.json({'status': 'failed', 'error': true, 'message': 'The captcha validation failed'});
|
|
} else {
|
|
// filter bad words if enabled
|
|
filter_bad_words((req.body.message == null || req.body.message == '' ? '' : req.body.message), function(claim_error, message) {
|
|
// check if there was an error or if the message was filtered
|
|
if (claim_error != null) {
|
|
// an error occurred with loading the bad-words filter
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Error loading the bad-words filter: ' + claim_error});
|
|
} else if (message == req.body.message) {
|
|
// call the verifymessage api
|
|
lib.verify_message(req.body.address, req.body.signature, req.body.message, function(body) {
|
|
if (body == false)
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Invalid signature'});
|
|
else if (body == true) {
|
|
db.update_claim_name(req.body.address, req.body.message, function(val) {
|
|
// check if the update was successful
|
|
if (val == '')
|
|
res.json({'status': 'success'});
|
|
else if (val == 'no_address')
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Wallet address ' + req.body.address + ' is not valid or does not have any transactions'});
|
|
else
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Wallet address or signature is invalid'});
|
|
});
|
|
} else
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Wallet address or signature is invalid'});
|
|
});
|
|
} else {
|
|
// message was filtered which would change the signature
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Display name contains bad words and cannot be saved: ' + message});
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
function validate_captcha(captcha_enabled, data, cb) {
|
|
// check if captcha is enabled for the requested feature
|
|
if (captcha_enabled == true) {
|
|
// determine the captcha type
|
|
if (settings.captcha.google_recaptcha3.enabled == true) {
|
|
if (data.google_recaptcha3 != null) {
|
|
const request = require('postman-request');
|
|
|
|
request({uri: 'https://www.google.com/recaptcha/api/siteverify?secret=' + settings.captcha.google_recaptcha3.secret_key + '&response=' + data.google_recaptcha3, json: true}, function (error, response, body) {
|
|
if (error) {
|
|
// an error occurred while trying to validate the captcha
|
|
return cb(true);
|
|
} else if (body == null || body == '' || typeof body !== 'object') {
|
|
// return data is invalid
|
|
return cb(true);
|
|
} else if (body.score == null || body.score < settings.captcha.google_recaptcha3.pass_score) {
|
|
// captcha challenge failed
|
|
return cb(true);
|
|
} else {
|
|
// captcha challenge passed
|
|
return cb(false);
|
|
}
|
|
});
|
|
} else {
|
|
// a captcha response wasn't received
|
|
return cb(true);
|
|
}
|
|
} else if (settings.captcha.google_recaptcha2.enabled == true) {
|
|
if (data.google_recaptcha2 != null) {
|
|
const request = require('postman-request');
|
|
|
|
request({uri: 'https://www.google.com/recaptcha/api/siteverify?secret=' + settings.captcha.google_recaptcha2.secret_key + '&response=' + data.google_recaptcha2, json: true}, function (error, response, body) {
|
|
if (error) {
|
|
// an error occurred while trying to validate the captcha
|
|
return cb(true);
|
|
} else if (body == null || body == '' || typeof body !== 'object') {
|
|
// return data is invalid
|
|
return cb(true);
|
|
} else if (body.success == null || body.success == false) {
|
|
// captcha challenge failed
|
|
return cb(true);
|
|
} else {
|
|
// captcha challenge passed
|
|
return cb(false);
|
|
}
|
|
});
|
|
} else {
|
|
// a captcha response wasn't received
|
|
return cb(true);
|
|
}
|
|
} else if (settings.captcha.hcaptcha.enabled == true) {
|
|
if (data.hcaptcha != null) {
|
|
const request = require('postman-request');
|
|
|
|
request({uri: 'https://hcaptcha.com/siteverify?secret=' + settings.captcha.hcaptcha.secret_key + '&response=' + data.hcaptcha, json: true}, function (error, response, body) {
|
|
if (error) {
|
|
// an error occurred while trying to validate the captcha
|
|
return cb(true);
|
|
} else if (body == null || body == '' || typeof body !== 'object') {
|
|
// return data is invalid
|
|
return cb(true);
|
|
} else if (body.success == null || body.success == false) {
|
|
// captcha challenge failed
|
|
return cb(true);
|
|
} else {
|
|
// captcha challenge passed
|
|
return cb(false);
|
|
}
|
|
});
|
|
} else {
|
|
// a captcha response wasn't received
|
|
return cb(true);
|
|
}
|
|
} else {
|
|
// no captcha options are enabled
|
|
return cb(false);
|
|
}
|
|
} else {
|
|
// captcha is not enabled for this feature
|
|
return cb(false);
|
|
}
|
|
}
|
|
|
|
function filter_bad_words(msg, cb) {
|
|
// check if the bad-words filter is enabled
|
|
if (settings.claim_address_page.enable_bad_word_filter == true) {
|
|
// import the bad-words dependency
|
|
import('bad-words').then(function(module) {
|
|
// load the bad-words filter
|
|
const bad_word_lib = module.Filter;
|
|
const bad_word_filter = new bad_word_lib();
|
|
|
|
// return the filtered msg
|
|
return cb(null, bad_word_filter.clean(msg));
|
|
})
|
|
.catch(function(err) {
|
|
return cb(err, null);
|
|
});
|
|
} else {
|
|
// return the msg without filtering for bad words
|
|
return cb(null, msg);
|
|
}
|
|
}
|
|
|
|
// post method to receive data from a plugin
|
|
app.post('/plugin-request', function(req, res) {
|
|
const pluginLockName = 'plugin';
|
|
|
|
// check if another plugin request is already running
|
|
if (lib.is_locked([pluginLockName], true) == true)
|
|
res.json({'status': 'failed', 'error': true, 'message': `Another plugin request is already running..`});
|
|
else {
|
|
// create a new plugin lock before checking the rest of the locks to minimize problems with running scripts at the same time
|
|
lib.create_lock(pluginLockName);
|
|
|
|
// check the backup, restore and delete locks since those functions would be problematic when updating data
|
|
if (lib.is_locked(['backup', 'restore', 'delete'], true) == true) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `Another script has locked the database..`});
|
|
} else {
|
|
// all lock tests passed. OK to run plugin request
|
|
|
|
let dataObject = {};
|
|
|
|
try {
|
|
// attempt to parse the POST data field into a JSON object
|
|
dataObject = JSON.parse(req.body.data);
|
|
} catch {
|
|
// do nothing. errors will be handled below
|
|
}
|
|
|
|
// check if the dataObject was populated
|
|
if (dataObject == null || JSON.stringify(dataObject) === '{}') {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': 'POST data is missing or not in the correct format'});
|
|
} else {
|
|
// check if the plugin secret code is correct and if the coin name was specified
|
|
if (dataObject.plugin_data == null || settings.plugins.plugin_secret_code != dataObject.plugin_data.secret_code) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Secret code is missing or incorrect'});
|
|
} else if (dataObject.plugin_data.coin_name == null || dataObject.plugin_data.coin_name == '') {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': 'Coin name is missing'});
|
|
} else {
|
|
const tableData = dataObject.table_data;
|
|
|
|
// check if the table_data seems valid
|
|
if (tableData == null || !Array.isArray(tableData)) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `table_data from POST data is missing or empty`});
|
|
} else {
|
|
const pluginName = (dataObject.plugin_data.plugin_name == null ? '' : dataObject.plugin_data.plugin_name);
|
|
const pluginObj = settings.plugins.allowed_plugins.find(item => item.plugin_name === pluginName && pluginName != '');
|
|
|
|
// check if the requested plugin was found in the settings
|
|
if (pluginObj == null) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `Plugin '${pluginName}' is not defined in settings`});
|
|
} else {
|
|
// check if the requested plugin is enabled
|
|
if (!pluginObj.enabled) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `Plugin '${pluginName}' is not enabled`});
|
|
} else {
|
|
// check if the plugin exists in the plugins directory
|
|
if (!db.fs.existsSync(`./plugins/${pluginName}`)) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `Plugin '${pluginName}' is not installed in the plugins directory`});
|
|
} else {
|
|
// check if the plugin's server_functions file exists
|
|
if (!db.fs.existsSync(`./plugins/${pluginName}/lib/server_functions.js`)) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `Plugin '${pluginName}' is missing the /lib/server_functions.js file`});
|
|
} else {
|
|
// load the server_functions.js file from the plugin
|
|
const serverFunctions = require(`./plugins/${pluginName}/lib/server_functions`);
|
|
|
|
// check if the process_plugin_request function exists
|
|
if (typeof serverFunctions.process_plugin_request !== 'function') {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json({'status': 'failed', 'error': true, 'message': `Plugin '${pluginName}' is missing the process_plugin_request function`});
|
|
} else {
|
|
// call the process_plugin_request function to process the new table data
|
|
serverFunctions.process_plugin_request(dataObject.plugin_data.coin_name, tableData, settings.sync.update_timeout, function(response) {
|
|
lib.remove_lock(pluginLockName);
|
|
res.json(response);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// extended apis
|
|
app.use('/ext/getmoneysupply', function(req, res) {
|
|
// check if the getmoneysupply api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getmoneysupply.enabled == true) {
|
|
// lookup stats
|
|
db.get_stats(settings.coin.name, function (stats) {
|
|
res.setHeader('content-type', 'text/plain');
|
|
res.end((stats && stats.supply ? stats.supply.toString() : '0'));
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getaddress/:hash', function(req, res) {
|
|
// check if the getaddress api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getaddress.enabled == true) {
|
|
db.get_address(req.params.hash, false, function(address) {
|
|
db.get_address_txs_ajax(req.params.hash, 0, settings.api_page.public_apis.ext.getaddresstxs.max_items_per_query, function(txs, count) {
|
|
if (address) {
|
|
var last_txs = [];
|
|
|
|
for (i = 0; i < txs.length; i++) {
|
|
if (typeof txs[i].txid !== "undefined") {
|
|
var out = 0,
|
|
vin = 0,
|
|
tx_type = 'vout',
|
|
row = {};
|
|
|
|
txs[i].vout.forEach(function (r) {
|
|
if (r.addresses == req.params.hash)
|
|
out += r.amount;
|
|
});
|
|
|
|
txs[i].vin.forEach(function (s) {
|
|
if (s.addresses == req.params.hash)
|
|
vin += s.amount;
|
|
});
|
|
|
|
if (vin > out)
|
|
tx_type = 'vin';
|
|
|
|
row['addresses'] = txs[i].txid;
|
|
row['type'] = tx_type;
|
|
last_txs.push(row);
|
|
}
|
|
}
|
|
|
|
var a_ext = {
|
|
address: address.a_id,
|
|
sent: (address.sent / 100000000),
|
|
received: (address.received / 100000000),
|
|
balance: (address.balance / 100000000).toString().replace(/(^-+)/mg, ''),
|
|
last_txs: last_txs
|
|
};
|
|
|
|
res.send(a_ext);
|
|
} else
|
|
res.send({ error: 'address not found.', hash: req.params.hash});
|
|
});
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/gettx/:txid', function(req, res) {
|
|
// check if the gettx api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.gettx.enabled == true) {
|
|
var txid = req.params.txid;
|
|
|
|
db.get_tx(txid, function(tx) {
|
|
if (tx) {
|
|
lib.get_blockcount(function(blockcount) {
|
|
res.send({ active: 'tx', tx: tx, confirmations: (blockcount - tx.blockindex + 1), blockcount: (blockcount ? blockcount : 0)});
|
|
});
|
|
} else {
|
|
lib.get_rawtransaction(txid, function(rtx) {
|
|
if (rtx && rtx.txid) {
|
|
lib.prepare_vin(rtx, function(vin, tx_type_vin) {
|
|
lib.prepare_vout(rtx.vout, rtx.txid, vin, ((typeof rtx.vjoinsplit === 'undefined' || rtx.vjoinsplit == null) ? [] : rtx.vjoinsplit), function(rvout, rvin, tx_type_vout) {
|
|
const total = lib.calculate_total(rvout);
|
|
|
|
if (!rtx.confirmations > 0) {
|
|
var utx = {
|
|
txid: rtx.txid,
|
|
vin: rvin,
|
|
vout: rvout,
|
|
total: total.toFixed(8),
|
|
timestamp: rtx.time,
|
|
blockhash: '-',
|
|
blockindex: -1
|
|
};
|
|
|
|
res.send({ active: 'tx', tx: utx, confirmations: rtx.confirmations, blockcount:-1});
|
|
} else {
|
|
var utx = {
|
|
txid: rtx.txid,
|
|
vin: rvin,
|
|
vout: rvout,
|
|
total: total.toFixed(8),
|
|
timestamp: rtx.time,
|
|
blockhash: rtx.blockhash,
|
|
blockindex: rtx.blockheight
|
|
};
|
|
|
|
lib.get_blockcount(function(blockcount) {
|
|
res.send({ active: 'tx', tx: utx, confirmations: rtx.confirmations, blockcount: (blockcount ? blockcount : 0)});
|
|
});
|
|
}
|
|
});
|
|
});
|
|
} else
|
|
res.send({ error: 'tx not found.', hash: txid});
|
|
});
|
|
}
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getbalance/:hash', function(req, res) {
|
|
// check if the getbalance api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getbalance.enabled == true) {
|
|
db.get_address(req.params.hash, false, function(address) {
|
|
if (address) {
|
|
res.setHeader('content-type', 'text/plain');
|
|
res.end((address.balance / 100000000).toString().replace(/(^-+)/mg, ''));
|
|
} else
|
|
res.send({ error: 'address not found.', hash: req.params.hash });
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getdistribution', function(req, res) {
|
|
// check if the getdistribution api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getdistribution.enabled == true) {
|
|
db.get_richlist(settings.coin.name, function(richlist) {
|
|
db.get_stats(settings.coin.name, function(stats) {
|
|
db.get_distribution(richlist, stats, function(dist) {
|
|
res.send(dist);
|
|
});
|
|
});
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getcurrentprice', function(req, res) {
|
|
// check if the getcurrentprice api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getcurrentprice.enabled == true) {
|
|
db.get_stats(settings.coin.name, function (stats) {
|
|
const currency = lib.get_market_currency_code();
|
|
|
|
eval('var p_ext = { "last_price_' + currency.toLowerCase() + '": stats.last_price, "last_price_usd": stats.last_usd_price, }');
|
|
res.send(p_ext);
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getbasicstats', function(req, res) {
|
|
// check if the getbasicstats api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getbasicstats.enabled == true) {
|
|
// lookup stats
|
|
db.get_stats(settings.coin.name, function (stats) {
|
|
const currency = lib.get_market_currency_code();
|
|
|
|
// check if the masternode count api is enabled
|
|
if (settings.api_page.public_apis.rpc.getmasternodecount.enabled == true && settings.api_cmds['getmasternodecount'] != null && settings.api_cmds['getmasternodecount'] != '') {
|
|
// masternode count api is available
|
|
lib.get_masternodecount(function(masternodestotal) {
|
|
eval('var p_ext = { "block_count": (stats.count ? stats.count : 0), "money_supply": (stats.supply ? stats.supply : 0), "last_price_' + currency.toLowerCase() + '": stats.last_price, "last_price_usd": stats.last_usd_price, "masternode_count": masternodestotal.total }');
|
|
res.send(p_ext);
|
|
});
|
|
} else {
|
|
// masternode count api is not available
|
|
eval('var p_ext = { "block_count": (stats.count ? stats.count : 0), "money_supply": (stats.supply ? stats.supply : 0), "last_price_' + currency.toLowerCase() + '": stats.last_price, "last_price_usd": stats.last_usd_price }');
|
|
res.send(p_ext);
|
|
}
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getlasttxs/:min', function(req, res) {
|
|
// check if the getlasttxs api is enabled or else check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if ((settings.api_page.enabled == true && settings.api_page.public_apis.ext.getlasttxs.enabled == true) || (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1)) {
|
|
var min = req.params.min, start, length, internal = false;
|
|
// split url suffix by forward slash and remove blank entries
|
|
var split = req.url.split('/').filter(function(v) { return v; });
|
|
// determine how many parameters were passed
|
|
switch (split.length) {
|
|
case 2:
|
|
// capture start and length
|
|
start = split[0];
|
|
length = split[1];
|
|
break;
|
|
default:
|
|
if (split.length == 1) {
|
|
// capture start
|
|
start = split[0];
|
|
} else if (split.length >= 2) {
|
|
// capture start and length
|
|
start = split[0];
|
|
length = split[1];
|
|
// check if this is an internal request
|
|
if (split.length > 2 && split[2] == 'internal')
|
|
internal = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// fix parameters
|
|
if (typeof length === 'undefined' || isNaN(length) || length > settings.api_page.public_apis.ext.getlasttxs.max_items_per_query)
|
|
length = settings.api_page.public_apis.ext.getlasttxs.max_items_per_query;
|
|
if (typeof start === 'undefined' || isNaN(start) || start < 0)
|
|
start = 0;
|
|
if (typeof min === 'undefined' || isNaN(min) || min < 0)
|
|
min = 0;
|
|
else
|
|
min = (min * 100000000);
|
|
|
|
db.get_last_txs(start, length, min, internal, function(data, count) {
|
|
// check if this is an internal request
|
|
if (internal) {
|
|
// display data formatted for internal datatable
|
|
res.json({"data": data, "recordsTotal": count, "recordsFiltered": count});
|
|
} else {
|
|
// display data in more readable format for public api
|
|
res.json(data);
|
|
}
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getaddresstxs/:address/:start/:length', function(req, res) {
|
|
// check if the getaddresstxs api is enabled or else check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if ((settings.api_page.enabled == true && settings.api_page.public_apis.ext.getaddresstxs.enabled == true) || (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1)) {
|
|
var internal = false;
|
|
// split url suffix by forward slash and remove blank entries
|
|
var split = req.url.split('/').filter(function(v) { return v; });
|
|
// check if this is an internal request
|
|
if (split.length > 0 && split[0] == 'internal')
|
|
internal = true;
|
|
// fix parameters
|
|
if (typeof req.params.length === 'undefined' || isNaN(req.params.length) || req.params.length > settings.api_page.public_apis.ext.getaddresstxs.max_items_per_query)
|
|
req.params.length = settings.api_page.public_apis.ext.getaddresstxs.max_items_per_query;
|
|
if (typeof req.params.start === 'undefined' || isNaN(req.params.start) || req.params.start < 0)
|
|
req.params.start = 0;
|
|
if (typeof req.params.min === 'undefined' || isNaN(req.params.min) || req.params.min < 0)
|
|
req.params.min = 0;
|
|
else
|
|
req.params.min = (req.params.min * 100000000);
|
|
|
|
db.get_address_txs_ajax(req.params.address, req.params.start, req.params.length, function(txs, count) {
|
|
var data = [];
|
|
|
|
for (i = 0; i < txs.length; i++) {
|
|
if (typeof txs[i].txid !== "undefined") {
|
|
var out = 0;
|
|
var vin = 0;
|
|
|
|
txs[i].vout.forEach(function(r) {
|
|
if (r.addresses == req.params.address)
|
|
out += r.amount;
|
|
});
|
|
|
|
txs[i].vin.forEach(function(s) {
|
|
if (s.addresses == req.params.address)
|
|
vin += s.amount;
|
|
});
|
|
|
|
if (internal) {
|
|
var row = [];
|
|
|
|
row.push(txs[i].timestamp);
|
|
row.push(txs[i].txid);
|
|
row.push(Number(out / 100000000));
|
|
row.push(Number(vin / 100000000));
|
|
row.push(Number(txs[i].balance / 100000000));
|
|
|
|
data.push(row);
|
|
} else {
|
|
data.push({
|
|
timestamp: txs[i].timestamp,
|
|
txid: txs[i].txid,
|
|
sent: Number(out / 100000000),
|
|
received: Number(vin / 100000000),
|
|
balance: Number(txs[i].balance / 100000000)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if this is an internal request
|
|
if (internal) {
|
|
// display data formatted for internal datatable
|
|
res.json({"data": data, "recordsTotal": count, "recordsFiltered": count});
|
|
} else {
|
|
// display data in more readable format for public api
|
|
res.json(data);
|
|
}
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
function get_connection_and_block_counts(get_data, cb) {
|
|
// check if the connection and block counts should be returned
|
|
if (get_data) {
|
|
lib.get_connectioncount(function(connections) {
|
|
lib.get_blockcount(function(blockcount) {
|
|
return cb(connections, blockcount);
|
|
});
|
|
});
|
|
} else
|
|
return cb(null, null);
|
|
}
|
|
|
|
app.use('/ext/getsummary', function(req, res) {
|
|
const isInternal = (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1);
|
|
|
|
// check if the getsummary api is enabled or else check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if ((settings.api_page.enabled == true && settings.api_page.public_apis.ext.getsummary.enabled == true) || isInternal) {
|
|
// check if this is a footer-only method that should only return the connection count and block count
|
|
if (req.headers['footer-only'] != null && req.headers['footer-only'] == 'true') {
|
|
// only return the connection count and block count
|
|
get_connection_and_block_counts(true, function(connections, blockcount) {
|
|
res.send({
|
|
connections: (connections ? connections : '-'),
|
|
blockcount: (blockcount ? blockcount : '-')
|
|
});
|
|
});
|
|
} else {
|
|
// get the connection and block counts only if this is NOT an internal call
|
|
get_connection_and_block_counts(!isInternal, function(connections, blockcount) {
|
|
lib.get_hashrate(function(hashrate) {
|
|
db.get_stats(settings.coin.name, function (stats) {
|
|
lib.get_masternodecount(function(masternodestotal) {
|
|
lib.get_difficulty(function(difficulty) {
|
|
let difficultyHybrid = '';
|
|
|
|
if (difficulty && difficulty['proof-of-work']) {
|
|
if (settings.shared_pages.difficulty == 'Hybrid') {
|
|
difficultyHybrid = 'POS: ' + difficulty['proof-of-stake'];
|
|
difficulty = 'POW: ' + difficulty['proof-of-work'];
|
|
} else if (settings.shared_pages.difficulty == 'POW')
|
|
difficulty = difficulty['proof-of-work'];
|
|
else
|
|
difficulty = difficulty['proof-of-stake'];
|
|
}
|
|
|
|
if (hashrate == `${settings.localization.ex_error}: ${settings.localization.check_console}`)
|
|
hashrate = 0;
|
|
|
|
let mn_total = 0;
|
|
let mn_enabled = 0;
|
|
|
|
// check if the masternode count api is enabled
|
|
if (settings.api_page.public_apis.rpc.getmasternodecount.enabled == true && settings.api_cmds['getmasternodecount'] != null && settings.api_cmds['getmasternodecount'] != '') {
|
|
// masternode count api is available
|
|
if (masternodestotal) {
|
|
if (masternodestotal.total)
|
|
mn_total = masternodestotal.total;
|
|
|
|
if (masternodestotal.enabled)
|
|
mn_enabled = masternodestotal.enabled;
|
|
}
|
|
}
|
|
|
|
res.send({
|
|
difficulty: (difficulty ? difficulty : '-'),
|
|
difficultyHybrid: difficultyHybrid,
|
|
supply: (stats == null || stats.supply == null ? 0 : stats.supply),
|
|
hashrate: hashrate,
|
|
lastPrice: (stats == null || stats.last_price == null ? 0 : stats.last_price),
|
|
lastUSDPrice: (stats == null || stats.last_usd_price == null ? 0 : stats.last_usd_price),
|
|
connections: (connections ? connections : '-'),
|
|
blockcount: (blockcount ? blockcount : '-'),
|
|
masternodeCountOnline: (masternodestotal && mn_enabled != 0 ? mn_enabled : '-'),
|
|
masternodeCountOffline: (masternodestotal && mn_total != 0 ? Math.floor(mn_total - mn_enabled) : '-')
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getnetworkpeers', function(req, res) {
|
|
// check if the getnetworkpeers api is enabled or else check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if ((settings.api_page.enabled == true && settings.api_page.public_apis.ext.getnetworkpeers.enabled == true) || (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1)) {
|
|
// get list of peers
|
|
db.get_peers(function(peers) {
|
|
// loop through peers list and remove the mongo _id and __v keys
|
|
for (i = 0; i < peers.length; i++) {
|
|
delete peers[i]['_doc']['_id'];
|
|
delete peers[i]['_doc']['__v'];
|
|
}
|
|
|
|
// sort ip6 addresses to the bottom
|
|
peers.sort(function(a, b) {
|
|
var address1 = a.address.indexOf(':') > -1;
|
|
var address2 = b.address.indexOf(':') > -1;
|
|
|
|
if (address1 < address2)
|
|
return -1;
|
|
else if (address1 > address2)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
});
|
|
|
|
// return peer data
|
|
res.json(peers);
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
// get the list of masternodes from local collection
|
|
app.use('/ext/getmasternodelist', function(req, res) {
|
|
// check if the getmasternodelist api is enabled or else check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if ((settings.api_page.enabled == true && settings.api_page.public_apis.ext.getmasternodelist.enabled == true) || (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1)) {
|
|
// get the masternode list from local collection
|
|
db.get_masternodes(function(masternodes) {
|
|
// loop through masternode list and remove the mongo _id and __v keys
|
|
for (i = 0; i < masternodes.length; i++) {
|
|
delete masternodes[i]['_doc']['_id'];
|
|
delete masternodes[i]['_doc']['__v'];
|
|
}
|
|
|
|
// return masternode list
|
|
res.send(masternodes);
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
// returns a list of masternode reward txs for a single masternode address from a specific block height
|
|
app.use('/ext/getmasternoderewards/:hash/:since', function(req, res) {
|
|
// check if the getmasternoderewards api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getmasternoderewards.enabled == true) {
|
|
db.get_masternode_rewards(req.params.hash, req.params.since, function(rewards) {
|
|
if (rewards != null) {
|
|
// loop through the tx list to fix vout values and remove unnecessary data such as the always empty vin array and the mongo _id and __v keys
|
|
for (i = 0; i < rewards.length; i++) {
|
|
// remove unnecessary data keys
|
|
delete rewards[i]['vin'];
|
|
delete rewards[i]['_id'];
|
|
delete rewards[i]['__v'];
|
|
// convert amounts from satoshis
|
|
rewards[i]['total'] = rewards[i]['total'] / 100000000;
|
|
rewards[i]['vout']['amount'] = rewards[i]['vout']['amount'] / 100000000;
|
|
}
|
|
|
|
// return list of masternode rewards
|
|
res.json(rewards);
|
|
} else
|
|
res.send({error: "failed to retrieve masternode rewards", hash: req.params.hash, since: req.params.since});
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
// returns the total masternode rewards received for a single masternode address from a specific block height
|
|
app.use('/ext/getmasternoderewardstotal/:hash/:since', function(req, res) {
|
|
// check if the getmasternoderewardstotal api is enabled
|
|
if (settings.api_page.enabled == true && settings.api_page.public_apis.ext.getmasternoderewardstotal.enabled == true) {
|
|
db.get_masternode_rewards_totals(req.params.hash, req.params.since, function(total_rewards) {
|
|
if (total_rewards != null) {
|
|
// return the total of masternode rewards
|
|
res.json(total_rewards);
|
|
} else
|
|
res.send({error: "failed to retrieve masternode rewards", hash: req.params.hash, since: req.params.since});
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
// get the list of orphans from local collection
|
|
app.use('/ext/getorphanlist/:start/:length', function(req, res) {
|
|
// check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1) {
|
|
// fix parameters
|
|
if (typeof req.params.start === 'undefined' || isNaN(req.params.start) || req.params.start < 0)
|
|
req.params.start = 0;
|
|
if (typeof req.params.length === 'undefined' || isNaN(req.params.length))
|
|
req.params.length = 10;
|
|
|
|
// get the orphan list from local collection
|
|
db.get_orphans(req.params.start, req.params.length, function(orphans, count) {
|
|
var data = [];
|
|
|
|
for (i = 0; i < orphans.length; i++) {
|
|
var row = [];
|
|
|
|
row.push(orphans[i].blockindex);
|
|
row.push(orphans[i].orphan_blockhash);
|
|
row.push(orphans[i].good_blockhash);
|
|
row.push(orphans[i].prev_blockhash);
|
|
row.push(orphans[i].next_blockhash);
|
|
|
|
data.push(row);
|
|
}
|
|
|
|
// display data formatted for internal datatable
|
|
res.json({"data": data, "recordsTotal": count, "recordsFiltered": count});
|
|
});
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
// get the last updated date for a particular section
|
|
app.use('/ext/getlastupdated/:section', function(req, res) {
|
|
// check the headers to see if it matches an internal ajax request from the explorer itself (TODO: come up with a more secure method of whitelisting ajax calls from the explorer)
|
|
if (req.headers['x-requested-with'] != null && req.headers['x-requested-with'].toLowerCase() == 'xmlhttprequest' && req.headers.referer != null && req.headers.accept.indexOf('text/javascript') > -1 && req.headers.accept.indexOf('application/json') > -1) {
|
|
// fix parameters
|
|
if (req.params.section == null)
|
|
req.params.section = '';
|
|
|
|
switch (req.params.section.toLowerCase()) {
|
|
case 'blockchain':
|
|
case 'movement':
|
|
// lookup last updated date
|
|
db.get_stats(settings.coin.name, function (stats) {
|
|
res.json({'last_updated_date': stats.blockchain_last_updated});
|
|
});
|
|
break;
|
|
default:
|
|
res.send({error: 'Cannot find last updated date'});
|
|
}
|
|
} else
|
|
res.end(settings.localization.method_disabled);
|
|
});
|
|
|
|
app.use('/ext/getnetworkchartdata', function(req, res) {
|
|
db.get_network_chart_data(function(data) {
|
|
if (data)
|
|
res.send(data);
|
|
else
|
|
res.send();
|
|
});
|
|
});
|
|
|
|
app.use('/system/restartexplorer', function(req, res, next) {
|
|
// check to ensure this special cmd is only executed by the local server
|
|
if (req._remoteAddress != null && req._remoteAddress.indexOf('127.0.0.1') > -1) {
|
|
// send a msg to the cluster process telling it to restart
|
|
process.send('restart');
|
|
res.end();
|
|
} else {
|
|
// show the error page
|
|
var err = new Error(settings.localization.error_not_found);
|
|
err.status = 404;
|
|
next(err);
|
|
}
|
|
});
|
|
|
|
var market_data = [];
|
|
var market_count = 0;
|
|
|
|
// check if markets are enabled
|
|
if (settings.markets_page.enabled == true) {
|
|
// dynamically populate market data
|
|
Object.keys(settings.markets_page.exchanges).forEach(function (key, index, map) {
|
|
// check if market is enabled via settings
|
|
if (settings.markets_page.exchanges[key].enabled == true) {
|
|
// check if market is installed/supported
|
|
if (db.fs.existsSync('./lib/markets/' + key + '.js')) {
|
|
// load market file
|
|
var exMarket = require('./lib/markets/' + key);
|
|
// save market_name and market_logo from market file to settings
|
|
eval('market_data.push({id: "' + key + '", name: "' + (exMarket.market_name == null ? '' : exMarket.market_name) + '", alt_name: "' + (exMarket.market_name_alt == null ? '' : exMarket.market_name_alt) + '", logo: "' + (exMarket.market_logo == null ? '' : exMarket.market_logo) + '", alt_logo: "' + (exMarket.market_logo_alt == null ? '' : exMarket.market_logo_alt) + '", trading_pairs: []});');
|
|
// loop through all trading pairs for this market
|
|
for (var i = 0; i < settings.markets_page.exchanges[key].trading_pairs.length; i++) {
|
|
var isAlt = false;
|
|
var pair = settings.markets_page.exchanges[key].trading_pairs[i].toUpperCase(); // ensure trading pair setting is always uppercase
|
|
var coin_symbol = pair.split('/')[0];
|
|
var pair_symbol = pair.split('/')[1];
|
|
|
|
// determine if using the alt name + logo
|
|
if (exMarket.market_url_template != null && exMarket.market_url_template != '') {
|
|
switch ((exMarket.market_url_case == null || exMarket.market_url_case == '' ? 'l' : exMarket.market_url_case.toLowerCase())) {
|
|
case 'l':
|
|
case 'lower':
|
|
isAlt = (exMarket.isAlt != null ? exMarket.isAlt({coin: coin_symbol.toLowerCase(), exchange: pair_symbol.toLowerCase()}) : false);
|
|
break;
|
|
case 'u':
|
|
case 'upper':
|
|
isAlt = (exMarket.isAlt != null ? exMarket.isAlt({coin: coin_symbol.toUpperCase(), exchange: pair_symbol.toUpperCase()}) : false);
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
|
|
// add trading pair to market_data
|
|
market_data[market_data.length - 1].trading_pairs.push({
|
|
pair: pair,
|
|
isAlt: isAlt
|
|
});
|
|
|
|
// increment the market count
|
|
market_count++;
|
|
}
|
|
|
|
// sort trading pairs by alt status
|
|
market_data[market_data.length - 1].trading_pairs.sort(function(a, b) {
|
|
if (a.isAlt < b.isAlt)
|
|
return -1;
|
|
else if (a.isAlt > b.isAlt)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// sort market data by market name
|
|
market_data.sort(function(a, b) {
|
|
var name1 = a.name.toLowerCase();
|
|
var name2 = b.name.toLowerCase();
|
|
|
|
if (name1 < name2)
|
|
return -1;
|
|
else if (name1 > name2)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
});
|
|
|
|
// fix default exchange name case
|
|
if (settings.markets_page.default_exchange.exchange_name != null)
|
|
settings.markets_page.default_exchange.exchange_name = settings.markets_page.default_exchange.exchange_name.toLowerCase();
|
|
else
|
|
settings.markets_page.default_exchange.exchange_name = '';
|
|
|
|
// fix default exchange trading pair case
|
|
if (settings.markets_page.default_exchange.trading_pair != null)
|
|
settings.markets_page.default_exchange.trading_pair = settings.markets_page.default_exchange.trading_pair.toUpperCase();
|
|
else
|
|
settings.markets_page.default_exchange.trading_pair = '';
|
|
|
|
var ex = settings.markets_page.exchanges;
|
|
var ex_name = settings.markets_page.default_exchange.exchange_name;
|
|
var ex_pair = settings.markets_page.default_exchange.trading_pair;
|
|
var ex_keys = Object.keys(ex);
|
|
var ex_error = '';
|
|
|
|
// check to ensure default market and trading pair exist and are enabled
|
|
if (ex[ex_name] == null) {
|
|
// exchange name does not exist in exchanges list
|
|
ex_error = 'Default exchange name is not valid' + ': ' + ex_name;
|
|
} else if (!ex[ex_name].enabled) {
|
|
// exchange is not enabled
|
|
ex_error = 'Default exchange is disabled in settings' + ': ' + ex_name;
|
|
} else if (ex[ex_name].trading_pairs.findIndex(p => p.toUpperCase() == ex_pair.toUpperCase()) == -1) {
|
|
// invalid default exchange trading pair
|
|
ex_error = 'Default exchange trading pair is not valid' + ': ' + ex_pair;
|
|
}
|
|
|
|
// check if there was an error msg
|
|
if (ex_error != '') {
|
|
// there was an error, so find the next available market from settings.json
|
|
var new_default_index = -1;
|
|
|
|
// find the first enabled exchange with at least one trading pair
|
|
for (var i = 0; i < ex_keys.length; i++) {
|
|
if (ex[ex_keys[i]]['enabled'] === true && ex[ex_keys[i]]['trading_pairs'].length > 0) {
|
|
// found a match so save the index
|
|
new_default_index = i;
|
|
// stop looking for more matches
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check if a valid and enabled market was found
|
|
if (new_default_index == -1) {
|
|
// no valid markets found
|
|
console.log('WARNING: ' + ex_error + '. ' + 'No valid or enabled markets found in settings.json. The markets feature will be temporarily disabled. To restore markets functionality, please enable at least 1 market and ensure at least 1 valid trading pair is added. Finally, restart the explorer to resolve the problem');
|
|
// disable the markets feature for this session
|
|
settings.markets_page.enabled = false;
|
|
} else {
|
|
// a valid and enabled market was found to replace the default
|
|
console.log('WARNING: ' + ex_error + '. ' + 'Default exchange will be set to' + ': ' + ex_keys[new_default_index] + '[' + ex[ex_keys[new_default_index]].trading_pairs[0].toUpperCase() + ']');
|
|
// set new default exchange data
|
|
settings.markets_page.default_exchange.exchange_name = ex_keys[new_default_index];
|
|
settings.markets_page.default_exchange.trading_pair = ex[ex_keys[new_default_index]].trading_pairs[0].toUpperCase();
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if home_link_logo file exists
|
|
if (!db.fs.existsSync(path.join('./public', settings.shared_pages.page_header.home_link_logo)))
|
|
settings.shared_pages.page_header.home_link_logo = '';
|
|
|
|
// always disable the rpc masternode list cmd from public apis
|
|
settings.api_page.public_apis.rpc.getmasternodelist = { "enabled": false };
|
|
|
|
// locals
|
|
app.set('explorer_version', package_metadata.version);
|
|
app.set('localization', settings.localization);
|
|
app.set('coin', settings.coin);
|
|
app.set('network_history', settings.network_history);
|
|
app.set('shared_pages', settings.shared_pages);
|
|
app.set('index_page', settings.index_page);
|
|
app.set('block_page', settings.block_page);
|
|
app.set('transaction_page', settings.transaction_page);
|
|
app.set('address_page', settings.address_page);
|
|
app.set('error_page', settings.error_page);
|
|
app.set('masternodes_page', settings.masternodes_page);
|
|
app.set('movement_page', settings.movement_page);
|
|
app.set('network_page', settings.network_page);
|
|
app.set('richlist_page', settings.richlist_page);
|
|
app.set('markets_page', settings.markets_page);
|
|
app.set('api_page', settings.api_page);
|
|
app.set('claim_address_page', settings.claim_address_page);
|
|
app.set('orphans_page', settings.orphans_page);
|
|
app.set('captcha', settings.captcha);
|
|
app.set('labels', settings.labels);
|
|
app.set('default_coingecko_ids', settings.default_coingecko_ids);
|
|
app.set('api_cmds', settings.api_cmds);
|
|
app.set('blockchain_specific', settings.blockchain_specific);
|
|
app.set('plugins', settings.plugins);
|
|
|
|
// determine panel offset based on which panels are enabled
|
|
var paneltotal = 5;
|
|
var panelcount = (settings.shared_pages.page_header.panels.network_panel.enabled == true && settings.shared_pages.page_header.panels.network_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.difficulty_panel.enabled == true && settings.shared_pages.page_header.panels.difficulty_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.masternodes_panel.enabled == true && settings.shared_pages.page_header.panels.masternodes_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.coin_supply_panel.enabled == true && settings.shared_pages.page_header.panels.coin_supply_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.price_panel.enabled == true && settings.shared_pages.page_header.panels.price_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.usd_price_panel.enabled == true && settings.shared_pages.page_header.panels.usd_price_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.market_cap_panel.enabled == true && settings.shared_pages.page_header.panels.market_cap_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.usd_market_cap_panel.enabled == true && settings.shared_pages.page_header.panels.usd_market_cap_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.logo_panel.enabled == true && settings.shared_pages.page_header.panels.logo_panel.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.spacer_panel_1.enabled == true && settings.shared_pages.page_header.panels.spacer_panel_1.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.spacer_panel_2.enabled == true && settings.shared_pages.page_header.panels.spacer_panel_2.display_order > 0 ? 1 : 0) +
|
|
(settings.shared_pages.page_header.panels.spacer_panel_3.enabled == true && settings.shared_pages.page_header.panels.spacer_panel_3.display_order > 0 ? 1 : 0);
|
|
app.set('paneloffset', paneltotal + 1 - panelcount);
|
|
|
|
// determine panel order
|
|
var panel_order = new Array();
|
|
|
|
if (settings.shared_pages.page_header.panels.network_panel.enabled == true && settings.shared_pages.page_header.panels.network_panel.display_order > 0) panel_order.push({name: 'network_panel', val: settings.shared_pages.page_header.panels.network_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.difficulty_panel.enabled == true && settings.shared_pages.page_header.panels.difficulty_panel.display_order > 0) panel_order.push({name: 'difficulty_panel', val: settings.shared_pages.page_header.panels.difficulty_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.masternodes_panel.enabled == true && settings.shared_pages.page_header.panels.masternodes_panel.display_order > 0) panel_order.push({name: 'masternodes_panel', val: settings.shared_pages.page_header.panels.masternodes_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.coin_supply_panel.enabled == true && settings.shared_pages.page_header.panels.coin_supply_panel.display_order > 0) panel_order.push({name: 'coin_supply_panel', val: settings.shared_pages.page_header.panels.coin_supply_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.price_panel.enabled == true && settings.shared_pages.page_header.panels.price_panel.display_order > 0) panel_order.push({name: 'price_panel', val: settings.shared_pages.page_header.panels.price_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.usd_price_panel.enabled == true && settings.shared_pages.page_header.panels.usd_price_panel.display_order > 0) panel_order.push({name: 'usd_price_panel', val: settings.shared_pages.page_header.panels.usd_price_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.market_cap_panel.enabled == true && settings.shared_pages.page_header.panels.market_cap_panel.display_order > 0) panel_order.push({name: 'market_cap_panel', val: settings.shared_pages.page_header.panels.market_cap_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.usd_market_cap_panel.enabled == true && settings.shared_pages.page_header.panels.usd_market_cap_panel.display_order > 0) panel_order.push({name: 'usd_market_cap_panel', val: settings.shared_pages.page_header.panels.usd_market_cap_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.logo_panel.enabled == true && settings.shared_pages.page_header.panels.logo_panel.display_order > 0) panel_order.push({name: 'logo_panel', val: settings.shared_pages.page_header.panels.logo_panel.display_order});
|
|
if (settings.shared_pages.page_header.panels.spacer_panel_1.enabled == true && settings.shared_pages.page_header.panels.spacer_panel_1.display_order > 0) panel_order.push({name: 'spacer_panel_1', val: settings.shared_pages.page_header.panels.spacer_panel_1.display_order});
|
|
if (settings.shared_pages.page_header.panels.spacer_panel_2.enabled == true && settings.shared_pages.page_header.panels.spacer_panel_2.display_order > 0) panel_order.push({name: 'spacer_panel_2', val: settings.shared_pages.page_header.panels.spacer_panel_2.display_order});
|
|
if (settings.shared_pages.page_header.panels.spacer_panel_3.enabled == true && settings.shared_pages.page_header.panels.spacer_panel_3.display_order > 0) panel_order.push({name: 'spacer_panel_3', val: settings.shared_pages.page_header.panels.spacer_panel_3.display_order});
|
|
|
|
panel_order.sort(function(a,b) { return a.val - b.val; });
|
|
|
|
for (var i = 1; i < 6; i++)
|
|
app.set('panel'+i.toString(), ((panel_order.length >= i) ? panel_order[i-1].name : ''));
|
|
|
|
app.set('market_data', market_data);
|
|
app.set('market_count', market_count);
|
|
|
|
// catch 404 and forward to error handler
|
|
app.use(function(req, res, next) {
|
|
var err = new Error('Not Found');
|
|
err.status = 404;
|
|
next(err);
|
|
});
|
|
|
|
// error handler - will print stacktrace when in development mode, otherwise no stacktraces will be leaked to the user
|
|
app.use(function(err, req, res, next) {
|
|
res.status(err.status || 500);
|
|
res.render('error', {
|
|
message: err.message,
|
|
error: (app.get('env') === 'development' ? err : {})
|
|
});
|
|
});
|
|
|
|
// determine if tls features should be enabled
|
|
if (settings.webserver.tls.enabled == true) {
|
|
function readCertsSync() {
|
|
var tls_options = {};
|
|
|
|
try {
|
|
tls_options = {
|
|
key: db.fs.readFileSync(settings.webserver.tls.key_file),
|
|
cert: db.fs.readFileSync(settings.webserver.tls.cert_file),
|
|
ca: db.fs.readFileSync(settings.webserver.tls.chain_file)
|
|
};
|
|
} catch(e) {
|
|
console.warn('There was a problem reading tls certificates. Check that the certificate, chain and key paths are correct.');
|
|
}
|
|
|
|
return tls_options;
|
|
}
|
|
|
|
const https = require('https');
|
|
let httpd = https.createServer(readCertsSync(), app).listen(settings.webserver.tls.port);
|
|
|
|
try {
|
|
let waitForCertsToRefresh;
|
|
|
|
// watch for changes to the certificate directory
|
|
db.fs.watch(path.dirname(settings.webserver.tls.key_file), () => {
|
|
clearTimeout(waitForCertsToRefresh);
|
|
|
|
// refresh certificates as they are changed on disk
|
|
waitForCertsToRefresh = setTimeout(() => {
|
|
httpd.setSecureContext(readCertsSync());
|
|
}, 1000);
|
|
});
|
|
} catch(e) {
|
|
console.warn('There was a problem reading tls certificates. Check that the certificate, chain and key paths are correct.');
|
|
}
|
|
}
|
|
|
|
// get the latest git commit id (if exists)
|
|
exec('git rev-parse HEAD', (err, stdout, stderr) => {
|
|
// check if the commit id was returned
|
|
if (stdout != null && stdout != '') {
|
|
// set the explorer revision code based on the git commit id
|
|
app.set('revision', stdout.substring(0, 7));
|
|
}
|
|
});
|
|
|
|
module.exports = app; |