Merge pull request #1137 from Ride-The-Lightning/Release-0.13.2

Release 0.13.2
This commit is contained in:
ShahanaFarooqui 2022-11-21 17:52:49 -08:00 committed by GitHub
commit 04f909329e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
407 changed files with 11149 additions and 5654 deletions

View File

@ -34,8 +34,9 @@
"@angular-eslint/arrow-body-style": "off",
"@angular-eslint/component-selector": ["error", { "prefix": "rtl", "style": "kebab-case", "type": "element" }],
"@angular-eslint/directive-selector": ["error", { "style": "camelCase", "type": "attribute" }],
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/member-delimiter-style": ["error", { "multiline": { "delimiter": "semi", "requireLast": true}, "singleline": { "delimiter": "comma", "requireLast": false }}],
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/type-annotation-spacing": "error",
"quotes": ["error", "single"],
"comma-dangle": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
@ -47,7 +48,7 @@
"curly": "error",
"no-unused-expressions": "error",
"strict": "error",
"max-len": ["error", { "code": 450 }],
"max-len": ["error", { "code": 320 }],
"no-multiple-empty-lines": "error",
"no-trailing-spaces": "error",
"quote-props": ["error", "as-needed"],

1
.github/README.md vendored
View File

@ -104,6 +104,7 @@ Example RTL-Config.json:
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"logLevel": "INFO",
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "<url for LND REST APIs for node #1 e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. https://localhost:8081>",
"boltzServerUrl": "<url for boltz server REST APIs for the node. e.g. https://localhost:9003>"

View File

@ -35,6 +35,7 @@ parameters have `default` values for initial setup and can be updated after RTL
"logLevel": <logging levels, will log in accordance with the logLevel value provided, Allowed values ERROR, WARN, INFO, DEBUG>,
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Required>,
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
"unannouncedChannels": <parameter to turn off/on setting for opening announced Channels, default false, Optional>
"lnServerUrl": "<Service url for LND/Core Lightning REST APIs for the node, e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001 OR http://192.168.0.1:8080. Default 'https://localhost:8080', Required",
"swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. https://localhost:8081, Optional>",
"boltzServerUrl": "<Service url for boltz server REST APIs for the node, e.g. https://localhost:9003, Optional>"
@ -66,4 +67,5 @@ RTL_CONFIG_PATH (Path for the folder containing 'RTL-Config.json' file, Required
BITCOIND_CONFIG_PATH (Full path of the bitcoind.conf file including the file name, Optional)<br />
CHANNEL_BACKUP_PATH (Folder location for saving the channel backup files, valid for LND implementation only, Required if ln implementation=LND else Optional)<br />
ENABLE_OFFERS (Boolean flag to enable the offers feature on core lighning, default false, optional)<br />
ENABLE_PEERSWAP (Boolean flag to enable the peerswap feature on core lighning, default false, optional)<br />
LN_API_PASSWORD (Password for Eclair implementation if the eclair.conf path is not available, Required if ln implementation=ECL && config path is undefined)<br />

View File

@ -82,6 +82,7 @@ Ensure that the follow values are correct per your config:
"bitcoindConfigPath": "",
"logLevel": "INFO",
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "https://<cl-rest api server ip address>:3001"
}
}

View File

@ -77,6 +77,7 @@ Ensure that the follow values are correct per your config:
"bitcoindConfigPath": "",
"logLevel": "INFO",
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "http://<eclair api server ip address>:port"
}
}

View File

@ -39,6 +39,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"logLevel": "INFO",
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "<https://<ip-address-of-device-running-lnd>:8080; e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<https://<localhost>:8081>",
"boltzServerUrl": "<https://<localhost>:9003>"

23
.github/workflows/stats.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Pull Request Stats
on:
push:
branches: [ master, 'Release-*' ]
tags: [ 'v*' ]
release:
types: [released]
# Triggers the workflow only when merging pull request to the branches.
pull_request:
types: [opened, closed]
branches: [ master, 'Release-*', '*' ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
stats:
runs-on: ubuntu-latest
steps:
- name: Run pull request stats
uses: flowwer-dev/pull-request-stats@master
with:
period: 365

View File

@ -27,7 +27,8 @@
"lnServerUrl": "https://localhost:8080",
"swapServerUrl": "https://localhost:8081",
"boltzServerUrl": "https://localhost:9003",
"fiatConversion": false
"fiatConversion": false,
"unannouncedChannels": false
}
}
]

View File

@ -62,7 +62,7 @@ export const getInfo = (req, res, next) => {
req.session.selectedNode.ln_version = body.version || '';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' });
clWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}

View File

@ -31,10 +31,6 @@ export const listInvoices = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List URL', data: options.url });
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
if (body.invoices && body.invoices.length > 0) {
body.invoices = common.sortDescByKey(body.invoices, 'expires_at');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Sorted Invoices List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Invoice', 'List Invoices Error', req.session.selectedNode);

View File

@ -78,8 +78,10 @@ export const listNodes = (req, res, next) => {
body.forEach((node) => {
var _a, _b;
if (node.option_will_fund) {
node.option_will_fund.lease_fee_base_msat = (node.option_will_fund.lease_fee_base_msat && typeof node.option_will_fund.lease_fee_base_msat === 'string' && node.option_will_fund.lease_fee_base_msat.includes('msat')) ? (_a = node.option_will_fund.lease_fee_base_msat) === null || _a === void 0 ? void 0 : _a.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.channel_fee_max_base_msat = (node.option_will_fund.channel_fee_max_base_msat && typeof node.option_will_fund.channel_fee_max_base_msat === 'string' && node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? (_b = node.option_will_fund.channel_fee_max_base_msat) === null || _b === void 0 ? void 0 : _b.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
node.option_will_fund.lease_fee_base_msat = (node.option_will_fund.lease_fee_base_msat && typeof node.option_will_fund.lease_fee_base_msat === 'string' &&
node.option_will_fund.lease_fee_base_msat.includes('msat')) ? (_a = node.option_will_fund.lease_fee_base_msat) === null || _a === void 0 ? void 0 : _a.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.channel_fee_max_base_msat = (node.option_will_fund.channel_fee_max_base_msat && typeof node.option_will_fund.channel_fee_max_base_msat === 'string' &&
node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? (_b = node.option_will_fund.channel_fee_max_base_msat) === null || _b === void 0 ? void 0 : _b.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
}
return node;
});

View File

@ -11,9 +11,6 @@ export const listOfferBookmarks = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Bookmarks..' });
databaseService.find(req.session.selectedNode, CollectionsEnum.OFFERS).then((offers) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmarks Received', data: offers });
if (offers && offers.length > 0) {
offers = common.sortDescByKey(offers, 'lastUpdatedAt');
}
res.status(200).json(offers);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Offers', 'Offer Bookmarks Error', req.session.selectedNode);
@ -22,7 +19,7 @@ export const listOfferBookmarks = (req, res, next) => {
};
export const deleteOfferBookmark = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' });
databaseService.destroy(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, req.params.offerStr).then((deleteRes) => {
databaseService.remove(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, req.params.offerStr).then((deleteRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: deleteRes });
res.status(204).json(req.params.offerStr);
}).catch((errRes) => {

View File

@ -44,9 +44,6 @@ export const getUTXOs = (req, res, next) => {
}
options.url = req.session.selectedNode.ln_server_url + '/v1/listFunds';
request(options).then((body) => {
if (body.outputs) {
body.outputs = common.sortDescByStrKey(body.outputs, 'status');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Funds List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {

View File

@ -73,10 +73,6 @@ export const listPayments = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/pay/listPayments';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body.payments });
if (body && body.payments && body.payments.length > 0) {
body.payments = common.sortDescByKey(body.payments, 'created_at');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sorted Payments List Received', data: body.payments });
res.status(200).json(groupBy(body.payments));
}).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);
@ -108,20 +104,26 @@ export const postPayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body });
if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB && req.body.bolt12) {
const offerToUpdate = { bolt12: req.body.bolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
const offerToUpdate = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.vendor) {
offerToUpdate['vendor'] = req.body.vendor;
}
if (req.body.description) {
offerToUpdate['description'] = req.body.description;
}
// eslint-disable-next-line arrow-body-style
return databaseService.validateDocument(CollectionsEnum.OFFERS, offerToUpdate).then((validated) => {
return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, req.body.bolt12).then((updatedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Updated', data: updatedOffer });
logger.log({ level: 'DEBUG', fileName: 'Payments', msg: 'Offer Updated', data: updatedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer });
}).catch((errDB) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB });
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
});
}).catch((errValidation) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB validation error', error: errValidation });
return res.status(201).json({ paymentResponse: body, saveToDBError: errValidation });
});
}
else {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });

View File

@ -17,9 +17,8 @@ export const getPeers = (req, res, next) => {
peer.alias = peer.id.substring(0, 20);
}
});
const peers = (body) ? common.sortDescByStrKey(body, 'alias') : [];
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers with Alias Received', data: peers });
res.status(200).json(peers);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers with Alias Received', data: body });
res.status(200).json(body || []);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -33,12 +32,11 @@ export const postPeer = (req, res, next) => {
}
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/connect';
options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: body });
request.post(options).then((connectRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: connectRes });
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/listPeers';
request(options).then((body) => {
let peers = (body) ? common.sortDescByStrKey(body, 'alias') : [];
peers = common.newestOnTop(peers, 'id', req.body.id);
request(options).then((listPeersRes) => {
const peers = listPeersRes ? common.newestOnTop(listPeersRes, 'id', req.body.id) : [];
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
res.status(201).json(peers);
}).catch((errRes) => {

View File

@ -13,10 +13,10 @@ export const simplifyAllChannels = (selNode, channels) => {
nodeId: channel.nodeId ? channel.nodeId : '',
channelId: channel.channelId ? channel.channelId : '',
state: channel.state ? channel.state : '',
channelFlags: channel.data && channel.data.commitments && channel.data.commitments.channelFlags ? channel.data.commitments.channelFlags : 0,
announceChannel: channel.data && channel.data.commitments && channel.data.commitments.channelFlags && channel.data.commitments.channelFlags.announceChannel ? channel.data.commitments.channelFlags.announceChannel : false,
toLocal: (channel.data.commitments.localCommit.spec.toLocal) ? Math.round(+channel.data.commitments.localCommit.spec.toLocal / 1000) : 0,
toRemote: (channel.data.commitments.localCommit.spec.toRemote) ? Math.round(+channel.data.commitments.localCommit.spec.toRemote / 1000) : 0,
shortChannelId: channel.data && channel.data.shortChannelId ? channel.data.shortChannelId : '',
shortChannelId: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.shortChannelId ? channel.data.channelUpdate.shortChannelId : '',
isFunder: channel.data && channel.data.commitments && channel.data.commitments.localParams && channel.data.commitments.localParams.isFunder ? channel.data.commitments.localParams.isFunder : false,
buried: channel.data && channel.data.buried ? channel.data.buried : false,
feeBaseMsat: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.feeBaseMsat ? channel.data.channelUpdate.feeBaseMsat : 0,

View File

@ -88,9 +88,6 @@ export const arrangePayments = (selNode, body) => {
relayedEle.amountOut = Math.round(relayedEle.amountOut / 1000);
}
});
payments.sent = common.sortDescByKey(payments.sent, 'firstPartTimestamp');
payments.received = common.sortDescByKey(payments.received, 'firstPartTimestamp');
payments.relayed = common.sortDescByKey(payments.relayed, 'timestamp');
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Fees', msg: 'Arranged Payments Received', data: payments });
return payments;
};

View File

@ -38,11 +38,11 @@ export const getInfo = (req, res, next) => {
body.lnImplementation = 'Eclair';
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
eclWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req);
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
}

View File

@ -71,10 +71,7 @@ export const listInvoices = (req, res, next) => {
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];
pendingInvoices = (!body[1] || body[1].length <= 0) ? [] : body[1];
return Promise.all(invoices === null || invoices === void 0 ? void 0 : invoices.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => {
body = common.sortDescByKey(invoices, 'expiresAt');
return res.status(200).json(invoices);
});
then((values) => res.status(200).json(invoices));
});
}
else {
@ -86,7 +83,6 @@ export const listInvoices = (req, res, next) => {
if (invoices && invoices.length > 0) {
return Promise.all(invoices === null || invoices === void 0 ? void 0 : invoices.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => {
body = common.sortDescByKey(invoices, 'expiresAt');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Sorted Invoices List Received', data: invoices });
return res.status(200).json(invoices);
}).

View File

@ -66,9 +66,6 @@ export const getTransactions = (req, res, next) => {
};
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Getting On Chain Transactions Options', data: options.form });
request.post(options).then((body) => {
if (body && body.length > 0) {
body = common.sortDescByKey(body, 'timestamp');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'On Chain Transactions Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {

View File

@ -37,7 +37,6 @@ export const getPeers = (req, res, next) => {
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
return peer;
});
body = common.sortDescByStrKey(body, 'alias');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body });
res.status(200).json(body);
});
@ -90,8 +89,7 @@ export const connectPeer = (req, res, next) => {
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
return peer;
});
let peers = (body) ? common.sortDescByStrKey(body, 'alias') : [];
peers = common.newestOnTop(peers, 'nodeId', req.query.nodeId ? req.query.nodeId : req.query.uri ? req.query.uri.substring(0, req.query.uri.indexOf('@')) : '');
const peers = common.newestOnTop(body || [], 'nodeId', req.query.nodeId ? req.query.nodeId : req.query.uri ? req.query.uri.substring(0, req.query.uri.indexOf('@')) : '');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
res.status(201).json(peers);
});

View File

@ -9,11 +9,11 @@ export const getAliasForChannel = (selNode, channel) => {
options.url = selNode.ln_server_url + '/v1/graph/node/' + pubkey;
return request(options).then((aliasBody) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Alias Received', data: aliasBody.node.alias });
channel.remote_alias = aliasBody.node.alias;
return aliasBody.node.alias;
channel.remote_alias = aliasBody.node.alias && aliasBody.node.alias !== '' ? aliasBody.node.alias : aliasBody.node.pub_key.slice(0, 20);
return channel;
}).catch((err) => {
channel.remote_alias = pubkey.slice(0, 10) + '...' + pubkey.slice(-10);
return pubkey;
channel.remote_alias = pubkey.slice(0, 20);
return channel;
});
};
export const getAllChannels = (req, res, next) => {
@ -38,7 +38,6 @@ export const getAllChannels = (req, res, next) => {
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return getAliasForChannel(req.session.selectedNode, channel);
})).then((values) => {
body.channels = common.sortDescByKey(body.channels, 'balancedness');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sorted Channels List Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {
@ -73,11 +72,11 @@ export const getPendingChannels = (req, res, next) => {
if (body.pending_open_channels && body.pending_open_channels.length > 0) {
(_a = body.pending_open_channels) === null || _a === void 0 ? void 0 : _a.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_closing_channels && body.pending_closing_channels.length > 0) {
(_b = body.pending_closing_channels) === null || _b === void 0 ? void 0 : _b.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_force_closing_channels && body.pending_force_closing_channels.length > 0) {
(_c = body.pending_force_closing_channels) === null || _c === void 0 ? void 0 : _c.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
(_b = body.pending_force_closing_channels) === null || _b === void 0 ? void 0 : _b.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_closing_channels && body.pending_closing_channels.length > 0) {
(_c = body.pending_closing_channels) === null || _c === void 0 ? void 0 : _c.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.waiting_close_channels && body.waiting_close_channels.length > 0) {
(_d = body.waiting_close_channels) === null || _d === void 0 ? void 0 : _d.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
@ -110,7 +109,6 @@ export const getClosedChannels = (req, res, next) => {
channel.close_type = (!channel.close_type) ? 'COOPERATIVE_CLOSE' : channel.close_type;
return getAliasForChannel(req.session.selectedNode, channel);
})).then((values) => {
body.channels = common.sortDescByKey(body.channels, 'close_height');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closed Channels List Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {
@ -161,7 +159,7 @@ export const postTransactions = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/transactions';
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/transaction-stream';
options.form = { payment_request: req.body.paymentReq };
if (req.body.paymentAmount) {
options.form.amt = req.body.paymentAmount;

View File

@ -45,7 +45,7 @@ export const getInfo = (req, res, next) => {
else {
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
lndWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}

View File

@ -10,7 +10,7 @@ export const getAliasFromPubkey = (selNode, pubkey) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Graph', msg: 'Alias Received', data: res.node.alias });
return res.node.alias;
}).
catch((err) => pubkey.substring(0, 17) + '...');
catch((err) => pubkey.substring(0, 20));
};
export const getDescribeGraph = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Getting Network Graph..' });

View File

@ -46,7 +46,6 @@ export const listInvoices = (req, res, next) => {
invoice.r_hash = invoice.r_hash ? Buffer.from(invoice.r_hash, 'base64').toString('hex') : '';
invoice.description_hash = invoice.description_hash ? Buffer.from(invoice.description_hash, 'base64').toString('hex') : null;
});
body.invoices = common.sortDescByKey(body.invoices, 'creation_date');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Sorted Invoices List Received', data: body });
res.status(200).json(body);
@ -62,18 +61,7 @@ export const addInvoice = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/invoices';
options.form = {
memo: req.body.memo,
private: req.body.private,
expiry: req.body.expiry
};
if (req.body.amount > 0 && req.body.amount < 1) {
options.form.value_msat = req.body.amount * 1000;
}
else {
options.form.value = req.body.amount;
}
options.form = JSON.stringify(options.form);
options.form = JSON.stringify(req.body);
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Added', data: body });
try {

View File

@ -58,10 +58,6 @@ export const getPayments = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=' + req.query.max_payments + '&index_offset=' + req.query.index_offset + '&reversed=' + req.query.reversed;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body });
if (body.payments && body.payments.length > 0) {
body.payments = common.sortDescByKey(body.payments, 'creation_date');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sorted Payments List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);

View File

@ -11,7 +11,7 @@ export const getAliasForPeers = (selNode, peer) => {
peer.alias = aliasBody.node.alias;
return aliasBody.node.alias;
}).catch((err) => {
peer.alias = peer.pub_key.slice(0, 10) + '...' + peer.pub_key.slice(-10);
peer.alias = peer.pub_key.slice(0, 20);
return peer.pub_key;
});
};
@ -26,10 +26,6 @@ export const getPeers = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
const peers = !body.peers ? [] : body.peers;
return Promise.all(peers === null || peers === void 0 ? void 0 : peers.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers with Alias before Sort', data: body });
if (body.peers) {
body.peers = common.sortDescByStrKey(body.peers, 'alias');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body.peers });
res.status(200).json(body.peers);
});
@ -56,7 +52,6 @@ export const postPeer = (req, res, next) => {
const peers = (!body.peers) ? [] : body.peers;
return Promise.all(peers === null || peers === void 0 ? void 0 : peers.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
if (body.peers) {
body.peers = common.sortDescByStrKey(body.peers, 'alias');
body.peers = common.newestOnTop(body.peers, 'pub_key', req.body.pubkey);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: body });
}

View File

@ -46,9 +46,6 @@ export const getAllForwardingEvents = (req, start, end, offset, caller, callback
}
if (!body.last_offset_index || body.last_offset_index < offset + num_max_events) {
responseData[caller].last_offset_index = body.last_offset_index ? body.last_offset_index : 0;
if (responseData[caller].forwarding_events) {
responseData[caller].forwarding_events = common.sortDescByKey(responseData[caller].forwarding_events, 'timestamp');
}
return callback(responseData[caller]);
}
else {

View File

@ -13,10 +13,6 @@ export const getTransactions = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/transactions';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Transactions', msg: 'Transactions List Received', data: body });
if (body.transactions && body.transactions.length > 0) {
body.transactions = common.sortDescByKey(body.transactions, 'time_stamp');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Transactions', msg: 'Sorted Transactions List Received', data: body.transactions });
res.status(200).json(body.transactions);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Transactions', 'List Transactions Error', req.session.selectedNode);

View File

@ -19,7 +19,7 @@ export const updateSelectedNode = (req, res, next) => {
if (req.headers && req.headers.authorization && req.headers.authorization !== '') {
wsServer.updateLNWSClientDetails(req.session.id, +req.session.selectedNode.index, +req.params.prevNodeIndex);
if (req.params.prevNodeIndex !== -1) {
databaseService.unloadDatabase(req.params.prevNodeIndex);
databaseService.unloadDatabase(req.params.prevNodeIndex, req.session.id);
}
}
const responseVal = !req.session.selectedNode.ln_node ? '' : req.session.selectedNode.ln_node;
@ -49,10 +49,11 @@ export const getRTLConfigInitial = (req, res, next) => {
const nodesArr = [];
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => {
const settings = {};
const settings = { unannouncedChannels: false };
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.unannouncedChannels = !!node.unannounced_channels || false;
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
@ -97,10 +98,11 @@ export const getRTLConfig = (req, res, next) => {
authentication.configPath = (node.config_path) ? node.config_path : '';
authentication.swapMacaroonPath = (node.swap_macaroon_path) ? node.swap_macaroon_path : '';
authentication.boltzMacaroonPath = (node.boltz_macaroon_path) ? node.boltz_macaroon_path : '';
const settings = {};
const settings = { unannouncedChannels: false };
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.unannouncedChannels = !!node.unannounced_channels || false;
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.bitcoindConfigPath = node.bitcoind_config_path;
settings.logLevel = node.log_level ? node.log_level : 'ERROR';
@ -108,6 +110,7 @@ export const getRTLConfig = (req, res, next) => {
settings.swapServerUrl = node.swap_server_url;
settings.boltzServerUrl = node.boltz_server_url;
settings.enableOffers = node.enable_offers;
settings.enablePeerswap = node.enable_peerswap;
settings.channelBackupPath = node.channel_backup_path;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
@ -134,6 +137,7 @@ export const updateUISettings = (req, res, next) => {
node.Settings.userPersona = req.body.updatedSettings.userPersona;
node.Settings.themeMode = req.body.updatedSettings.themeMode;
node.Settings.themeColor = req.body.updatedSettings.themeColor;
node.Settings.unannouncedChannels = req.body.updatedSettings.unannouncedChannels;
node.Settings.fiatConversion = req.body.updatedSettings.fiatConversion;
if (req.body.updatedSettings.fiatConversion) {
node.Settings.currencyUnit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
@ -145,6 +149,7 @@ export const updateUISettings = (req, res, next) => {
selectedNode.user_persona = req.body.updatedSettings.userPersona;
selectedNode.theme_mode = req.body.updatedSettings.themeMode;
selectedNode.theme_color = req.body.updatedSettings.themeColor;
selectedNode.unannounced_channels = req.body.updatedSettings.unannouncedChannels;
selectedNode.fiat_conversion = req.body.updatedSettings.fiatConversion;
if (req.body.updatedSettings.fiatConversion) {
selectedNode.currency_unit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
@ -244,7 +249,7 @@ export const getConfig = (req, res, next) => {
if (jsonConfig['Application Options'] && jsonConfig['Application Options'].color) {
jsonConfig['Application Options'].color = '#' + jsonConfig['Application Options'].color;
}
if (req.session.selectedNode.ln_implementation === 'ECL' && !jsonConfig['eclair.api.password']) {
if (req.params.nodeType === 'ln' && req.session.selectedNode.ln_implementation === 'ECL' && !jsonConfig['eclair.api.password']) {
fileFormat = 'HOCON';
jsonConfig = parseHocon(data);
}
@ -311,7 +316,7 @@ export const updateServiceSettings = (req, res, next) => {
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const selectedNode = common.findNode(req.session.selectedNode.index);
config.nodes.find((node) => {
config.nodes.forEach((node) => {
if (node.index === req.session.selectedNode.index) {
switch (req.body.service) {
case 'LOOP':
@ -346,6 +351,10 @@ export const updateServiceSettings = (req, res, next) => {
node.Settings.enableOffers = req.body.settings.enableOffers;
selectedNode.enable_offers = req.body.settings.enableOffers;
break;
case 'PEERSWAP':
node.Settings.enablePeerswap = req.body.settings.enablePeerswap;
selectedNode.enable_peerswap = req.body.settings.enablePeerswap;
break;
default:
break;
}
@ -374,7 +383,8 @@ export const maskPasswords = (obj) => {
}
if (typeof keys[i] === 'string' &&
(keys[i].toLowerCase().includes('password') || keys[i].toLowerCase().includes('multipass') ||
keys[i].toLowerCase().includes('rpcpass') || keys[i].toLowerCase().includes('rpcpassword'))) {
keys[i].toLowerCase().includes('rpcpass') || keys[i].toLowerCase().includes('rpcpassword') ||
keys[i].toLowerCase().includes('rpcuser'))) {
obj[keys[i]] = '********************';
}
}

View File

@ -124,7 +124,7 @@ export const resetPassword = (req, res, next) => {
export const logoutUser = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Logged out' });
if (req.session.selectedNode && req.session.selectedNode.index) {
databaseService.unloadDatabase(+req.session.selectedNode.index);
databaseService.unloadDatabase(+req.session.selectedNode.index, req.session.id);
}
req.session.destroy((err) => {
res.clearCookie('connect.sid');

View File

@ -216,10 +216,6 @@ export const swaps = (req, res, next) => {
options.url = options.url + '/v1/loop/swaps';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Swaps Received', data: body });
if (body.swaps && body.swaps.length > 0) {
body.swaps = common.sortDescByKey(body.swaps, 'initiation_time');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Sorted Loop Swaps List Received', data: body });
}
res.status(200).json(body.swaps);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Loop', 'List Swaps Error', req.session.selectedNode);

View File

@ -0,0 +1,33 @@
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { CollectionsEnum } from '../../models/database.model.js';
const logger = Logger;
const common = Common;
const databaseService = Database;
export const getPageSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Getting Page Settings..' });
databaseService.find(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS).then((settings) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Received', data: settings });
res.status(200).json(settings);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Page Settings', 'Page Settings Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const savePageSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Saving Page Settings..' });
// eslint-disable-next-line arrow-body-style
return Promise.all(req.body.map((page) => databaseService.validateDocument(CollectionsEnum.PAGE_SETTINGS, page))).then((values) => {
return databaseService.insert(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS, req.body).then((insertRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Updated', data: insertRes });
res.status(201).json(insertRes);
}).catch((insertErrRes) => {
const err = common.handleError(insertErrRes, 'Page Settings', 'Page Settings Update Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
}).catch((errRes) => {
const err = common.handleError(errRes, 'Page Settings', 'Page Settings Validation Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

View File

@ -1,5 +1,5 @@
export class CommonSelectedNode {
constructor(options, ln_server_url, macaroon_path, ln_api_password, swap_server_url, boltz_server_url, config_path, rtl_conf_file_path, swap_macaroon_path, boltz_macaroon_path, bitcoind_config_path, channel_backup_path, log_level, log_file, index, ln_node, ln_implementation, user_persona, theme_mode, theme_color, fiat_conversion, currency_unit, ln_version, api_version, enable_offers) {
constructor(options, ln_server_url, macaroon_path, ln_api_password, swap_server_url, boltz_server_url, config_path, rtl_conf_file_path, swap_macaroon_path, boltz_macaroon_path, bitcoind_config_path, channel_backup_path, log_level, log_file, index, ln_node, ln_implementation, user_persona, theme_mode, theme_color, unannounced_channels, fiat_conversion, currency_unit, ln_version, api_version, enable_offers, enable_peerswap) {
this.options = options;
this.ln_server_url = ln_server_url;
this.macaroon_path = macaroon_path;
@ -20,11 +20,13 @@ export class CommonSelectedNode {
this.user_persona = user_persona;
this.theme_mode = theme_mode;
this.theme_color = theme_color;
this.unannounced_channels = unannounced_channels;
this.fiat_conversion = fiat_conversion;
this.currency_unit = currency_unit;
this.ln_version = ln_version;
this.api_version = api_version;
this.enable_offers = enable_offers;
this.enable_peerswap = enable_peerswap;
}
}
export class AuthenticationConfiguration {
@ -35,10 +37,11 @@ export class AuthenticationConfiguration {
}
}
export class NodeSettingsConfiguration {
constructor(userPersona, themeMode, themeColor, fiatConversion, currencyUnit, bitcoindConfigPath, logLevel, lnServerUrl, swapServerUrl, boltzServerUrl, channelBackupPath, enableOffers) {
constructor(userPersona, themeMode, themeColor, unannouncedChannels, fiatConversion, currencyUnit, bitcoindConfigPath, logLevel, lnServerUrl, swapServerUrl, boltzServerUrl, channelBackupPath, enableOffers, enablePeerswap) {
this.userPersona = userPersona;
this.themeMode = themeMode;
this.themeColor = themeColor;
this.unannouncedChannels = unannouncedChannels;
this.fiatConversion = fiatConversion;
this.currencyUnit = currencyUnit;
this.bitcoindConfigPath = bitcoindConfigPath;
@ -48,6 +51,7 @@ export class NodeSettingsConfiguration {
this.boltzServerUrl = boltzServerUrl;
this.channelBackupPath = channelBackupPath;
this.enableOffers = enableOffers;
this.enablePeerswap = enablePeerswap;
}
}
export class LogJSONObj {

View File

@ -1,38 +1,139 @@
export var CollectionsEnum;
(function (CollectionsEnum) {
CollectionsEnum["OFFERS"] = "Offers";
})(CollectionsEnum || (CollectionsEnum = {}));
export var OfferFieldsEnum;
(function (OfferFieldsEnum) {
OfferFieldsEnum["BOLT12"] = "bolt12";
OfferFieldsEnum["AMOUNTMSAT"] = "amountmSat";
OfferFieldsEnum["AMOUNTMSAT"] = "amountMSat";
OfferFieldsEnum["TITLE"] = "title";
OfferFieldsEnum["VENDOR"] = "vendor";
OfferFieldsEnum["DESCRIPTION"] = "description";
})(OfferFieldsEnum || (OfferFieldsEnum = {}));
export const CollectionFieldsEnum = Object.assign({}, OfferFieldsEnum);
export class Offer {
constructor(bolt12, amountmSat, title, vendor, description, lastUpdatedAt) {
constructor(bolt12, amountMSat, title, vendor, description, lastUpdatedAt) {
this.bolt12 = bolt12;
this.amountmSat = amountmSat;
this.amountMSat = amountMSat;
this.title = title;
this.vendor = vendor;
this.description = description;
this.lastUpdatedAt = lastUpdatedAt;
}
}
export const validateDocument = (collectionName, documentToValidate) => {
switch (collectionName) {
case CollectionsEnum.OFFERS:
return validateOffer(documentToValidate);
case CollectionsEnum.PAGE_SETTINGS:
return validatePageSettings(documentToValidate);
default:
return ({ isValid: false, error: 'Collection does not exist' });
}
};
export const validateOffer = (documentToValidate) => {
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.BOLT12)) {
return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + 'is mandatory.' });
return ({ isValid: false, error: 'Bolt12 is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.AMOUNTMSAT)) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'is mandatory.' });
return ({ isValid: false, error: 'Amount mSat is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TITLE)) {
return ({ isValid: false, error: CollectionFieldsEnum.TITLE + 'is mandatory.' });
return ({ isValid: false, error: 'Title is mandatory.' });
}
if ((typeof documentToValidate[CollectionFieldsEnum.AMOUNTMSAT] !== 'number')) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'should be a number.' });
return ({ isValid: false, error: 'Amount mSat should be a number.' });
}
return ({ isValid: true });
};
export var SortOrderEnum;
(function (SortOrderEnum) {
SortOrderEnum["ASCENDING"] = "asc";
SortOrderEnum["DESCENDING"] = "desc";
})(SortOrderEnum || (SortOrderEnum = {}));
export var PageSettingsFieldsEnum;
(function (PageSettingsFieldsEnum) {
PageSettingsFieldsEnum["PAGE_ID"] = "pageId";
PageSettingsFieldsEnum["TABLES"] = "tables";
})(PageSettingsFieldsEnum || (PageSettingsFieldsEnum = {}));
export var TableSettingsFieldsEnum;
(function (TableSettingsFieldsEnum) {
TableSettingsFieldsEnum["TABLE_ID"] = "tableId";
TableSettingsFieldsEnum["RECORDS_PER_PAGE"] = "recordsPerPage";
TableSettingsFieldsEnum["SORT_BY"] = "sortBy";
TableSettingsFieldsEnum["SORT_ORDER"] = "sortOrder";
TableSettingsFieldsEnum["COLUMN_SELECTION"] = "columnSelection";
TableSettingsFieldsEnum["COLUMN_SELECTION_SM"] = "columnSelectionSM";
})(TableSettingsFieldsEnum || (TableSettingsFieldsEnum = {}));
export class TableSetting {
constructor(tableId, recordsPerPage, sortBy, sortOrder, columnSelection) {
this.tableId = tableId;
this.recordsPerPage = recordsPerPage;
this.sortBy = sortBy;
this.sortOrder = sortOrder;
this.columnSelection = columnSelection;
}
}
export class PageSettings {
constructor(pageId, tables) {
this.pageId = pageId;
this.tables = tables;
}
}
export const validatePageSettings = (documentToValidate) => {
let errorMessages = '';
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.PAGE_ID)) {
errorMessages = errorMessages + 'Page ID is mandatory.';
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TABLES)) {
errorMessages = errorMessages + 'Tables is mandatory.';
}
const tablesMessages = documentToValidate.tables.reduce((tableAcc, table, tableIdx) => {
let errMsg = '';
if (!table.hasOwnProperty(CollectionFieldsEnum.TABLE_ID)) {
errMsg = errMsg + 'Table ID is mandatory.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.SORT_BY)) {
errMsg = errMsg + 'Sort By is mandatory.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.SORT_ORDER)) {
errMsg = errMsg + 'Sort Order is mandatory.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.COLUMN_SELECTION_SM)) {
errMsg = errMsg + 'Column Selection (Mobile Resolution) is mandatory.';
}
if (table[CollectionFieldsEnum.COLUMN_SELECTION_SM].length < 1) {
errMsg = errMsg + 'Column Selection (Mobile Resolution) should have at least 1 field.';
}
if (table[CollectionFieldsEnum.COLUMN_SELECTION_SM].length > 3) {
errMsg = errMsg + 'Column Selection (Mobile Resolution) should have maximum 3 fields.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.COLUMN_SELECTION)) {
errMsg = errMsg + 'Column Selection (Desktop Resolution) is mandatory.';
}
if (table[CollectionFieldsEnum.COLUMN_SELECTION].length < 2) {
errMsg = errMsg + 'Column Selection (Desktop Resolution) should have at least 2 fields.';
}
if (errMsg.trim() !== '') {
tableAcc.push({ table: (table.hasOwnProperty(CollectionFieldsEnum.TABLE_ID) ? table[CollectionFieldsEnum.TABLE_ID] : (tableIdx + 1)), message: errMsg });
}
return tableAcc;
}, []);
if (errorMessages.trim() === '' && tablesMessages.length === 0) {
return ({ isValid: true });
}
else {
const errObj = { page: (documentToValidate.hasOwnProperty(CollectionFieldsEnum.PAGE_ID) ? documentToValidate[CollectionFieldsEnum.PAGE_ID] : 'Unknown') };
if (errorMessages.trim() !== '') {
errObj['message'] = errorMessages;
}
if (tablesMessages.length && tablesMessages.length > 0) {
errObj['tables'] = tablesMessages;
}
return ({ isValid: false, error: JSON.stringify(errObj) });
}
};
export var CollectionsEnum;
(function (CollectionsEnum) {
CollectionsEnum["OFFERS"] = "Offers";
CollectionsEnum["PAGE_SETTINGS"] = "PageSettings";
})(CollectionsEnum || (CollectionsEnum = {}));
export const CollectionFieldsEnum = Object.assign(Object.assign(Object.assign({}, OfferFieldsEnum), PageSettingsFieldsEnum), TableSettingsFieldsEnum);
export const LNDCollection = [CollectionsEnum.PAGE_SETTINGS];
export const ECLCollection = [CollectionsEnum.PAGE_SETTINGS];
export const CLNCollection = [CollectionsEnum.PAGE_SETTINGS, CollectionsEnum.OFFERS];

View File

@ -4,12 +4,14 @@ import authenticateRoutes from './authenticate.js';
import boltzRoutes from './boltz.js';
import loopRoutes from './loop.js';
import RTLConfRoutes from './RTLConf.js';
import pageSettingsRoutes from './pageSettings.js';
const router = Router();
const sharedRoutes = [
{ path: '/authenticate', route: authenticateRoutes },
{ path: '/boltz', route: boltzRoutes },
{ path: '/loop', route: loopRoutes },
{ path: '/conf', route: RTLConfRoutes }
{ path: '/conf', route: RTLConfRoutes },
{ path: '/pagesettings', route: pageSettingsRoutes }
];
sharedRoutes.forEach((route) => {
router.use(route.path, route.route);

View File

@ -0,0 +1,8 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getPageSettings, savePageSettings } from '../../controllers/shared/pageSettings.js';
const router = Router();
router.get('/', isAuthenticated, getPageSettings);
router.post('/', isAuthenticated, savePageSettings);
export default router;

View File

@ -41,13 +41,14 @@ export class ExpressApplication {
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
this.app.use((req, res, next) => {
// For Angular App
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '');
// For JQuery Browser Plugin
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '');
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Quickpay JQuery
res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html'));
});
this.app.use((err, req, res, next) => this.handleApplicationErrors(err, res));
this.app.use((err, req, res, next) => {
this.handleApplicationErrors(err, res);
next();
});
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' });
};
this.handleApplicationErrors = (err, res) => {

View File

@ -24,7 +24,10 @@ export class CommonService {
this.read_dummy_data = false;
this.baseHref = '/rtl';
this.dummy_data_array_from_file = [];
this.MONTHS = [{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 }, { name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }];
this.MONTHS = [
{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 },
{ name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }
];
this.getSwapServerOptions = (req) => {
const swapOptions = {
url: req.session.selectedNode.swap_server_url,
@ -253,7 +256,16 @@ export class CommonService {
break;
}
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') });
const newErrorObj = {
let newErrorObj = { statusCode: 500, message: '', error: '' };
if (err.code && err.code === 'ENOENT') {
newErrorObj = {
statusCode: 500,
message: 'No such file or directory ' + (err.path ? err.path : ''),
error: 'No such file or directory ' + (err.path ? err.path : '')
};
}
else {
newErrorObj = {
statusCode: err.statusCode ? err.statusCode : err.status ? err.status : (err.error && err.error.code && err.error.code === 'ECONNREFUSED') ? 503 : 500,
message: (err.error && err.error.message) ? err.error.message : err.message ? err.message : errMsg,
error: ((err.error && err.error.error && err.error.error.error && typeof err.error.error.error === 'string') ? err.error.error.error :
@ -263,6 +275,10 @@ export class CommonService {
(err.error && typeof err.error === 'string') ? err.error :
(err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error')
};
}
if (selectedNode.ln_implementation === 'ECL' && err.message.indexOf('Authentication Error') < 0 && err.name === 'StatusCodeError') {
newErrorObj.statusCode = 500;
}
return newErrorObj;
};
this.getRequestIP = (req) => ((typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) ||

View File

@ -66,12 +66,13 @@ export class ConfigService {
channelBackupPath: channelBackupPath,
logLevel: 'ERROR',
lnServerUrl: 'https://localhost:8080',
fiatConversion: false
fiatConversion: false,
unannouncedChannels: false
}
}
]
};
if (+process.env.RTL_SSO === 0) {
if (+process.env.RTL_SSO === 0 || configData.SSO.rtlSSO === 0) {
configData['multiPass'] = 'password';
}
return configData;
@ -211,6 +212,7 @@ export class ConfigService {
this.common.nodes[idx].user_persona = node.Settings.userPersona ? node.Settings.userPersona : 'MERCHANT';
this.common.nodes[idx].theme_mode = node.Settings.themeMode ? node.Settings.themeMode : 'DAY';
this.common.nodes[idx].theme_color = node.Settings.themeColor ? node.Settings.themeColor : 'PURPLE';
this.common.nodes[idx].unannounced_channels = node.Settings.unannouncedChannels ? !!node.Settings.unannouncedChannels : false;
this.common.nodes[idx].log_level = node.Settings.logLevel ? node.Settings.logLevel : 'ERROR';
this.common.nodes[idx].fiat_conversion = node.Settings.fiatConversion ? !!node.Settings.fiatConversion : false;
if (this.common.nodes[idx].fiat_conversion) {
@ -241,6 +243,7 @@ export class ConfigService {
this.common.nodes[idx].boltz_macaroon_path = '';
}
this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS ? process.env.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
this.common.nodes[idx].enable_peerswap = process.env.ENABLE_PEERSWAP ? process.env.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
try {

View File

@ -3,7 +3,7 @@ import { join, dirname, sep } from 'path';
import { fileURLToPath } from 'url';
import { Common } from '../utils/common.js';
import { Logger } from '../utils/logger.js';
import { CollectionsEnum, validateOffer } from '../models/database.model.js';
import { validateDocument, LNDCollection, ECLCollection, CLNCollection } from '../models/database.model.js';
export class DatabaseService {
constructor() {
this.common = Common;
@ -11,33 +11,68 @@ export class DatabaseService {
this.dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
this.nodeDatabase = {};
}
loadDatabase(selectedNode) {
loadDatabase(session) {
const { id, selectedNode } = session;
try {
if (!this.nodeDatabase[selectedNode.index]) {
this.nodeDatabase[selectedNode.index] = { adapter: null, data: null };
this.nodeDatabase[selectedNode.index] = { adapter: null, data: {} };
this.nodeDatabase[selectedNode.index].adapter = new DatabaseAdapter(this.dbDirectory, selectedNode, id);
this.fetchNodeData(selectedNode);
this.logger.log({ selectedNode: selectedNode, level: 'DEBUG', fileName: 'Database', msg: 'Database Loaded', data: this.nodeDatabase[selectedNode.index].data });
}
else {
this.nodeDatabase[selectedNode.index].adapter.insertSession(id);
}
this.nodeDatabase[selectedNode.index].adapter = new DatabaseAdapter(this.dbDirectory, 'rtldb', selectedNode);
this.nodeDatabase[selectedNode.index].data = this.nodeDatabase[selectedNode.index].adapter.fetchData();
}
catch (err) {
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: 'Database', msg: 'Database Load Error', error: err });
}
}
create(selectedNode, collectionName, newDocument) {
fetchNodeData(selectedNode) {
switch (selectedNode.ln_implementation) {
case 'CLN':
for (const collectionName in CLNCollection) {
if (CLNCollection.hasOwnProperty(collectionName)) {
this.nodeDatabase[selectedNode.index].data[CLNCollection[collectionName]] = this.nodeDatabase[selectedNode.index].adapter.fetchData(CLNCollection[collectionName]);
}
}
break;
case 'ECL':
for (const collectionName in ECLCollection) {
if (ECLCollection.hasOwnProperty(collectionName)) {
this.nodeDatabase[selectedNode.index].data[ECLCollection[collectionName]] = this.nodeDatabase[selectedNode.index].adapter.fetchData(ECLCollection[collectionName]);
}
}
break;
default:
for (const collectionName in LNDCollection) {
if (LNDCollection.hasOwnProperty(collectionName)) {
this.nodeDatabase[selectedNode.index].data[LNDCollection[collectionName]] = this.nodeDatabase[selectedNode.index].adapter.fetchData(LNDCollection[collectionName]);
}
}
break;
}
}
validateDocument(collectionName, newDocument) {
return new Promise((resolve, reject) => {
const validationRes = validateDocument(collectionName, newDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
}
else {
resolve(true);
}
});
}
insert(selectedNode, collectionName, newCollection) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, newDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
}
else {
this.nodeDatabase[selectedNode.index].data[collectionName].push(newDocument);
this.saveDatabase(+selectedNode.index);
resolve(newDocument);
}
this.nodeDatabase[selectedNode.index].data[collectionName] = newCollection;
this.saveDatabase(selectedNode, collectionName);
resolve(this.nodeDatabase[selectedNode.index].data[collectionName]);
}
catch (errRes) {
reject(errRes);
@ -64,11 +99,6 @@ export class DatabaseService {
}
updatedDocument = foundDoc;
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, updatedDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
}
else {
if (foundDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(foundDocIdx, 1, updatedDocument);
}
@ -78,10 +108,9 @@ export class DatabaseService {
}
this.nodeDatabase[selectedNode.index].data[collectionName].push(updatedDocument);
}
this.saveDatabase(+selectedNode.index);
this.saveDatabase(selectedNode, collectionName);
resolve(updatedDocument);
}
}
catch (errRes) {
reject(errRes);
}
@ -105,7 +134,7 @@ export class DatabaseService {
}
});
}
destroy(selectedNode, collectionName, documentFieldName, documentFieldValue) {
remove(selectedNode, collectionName, documentFieldName, documentFieldValue) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
@ -118,7 +147,7 @@ export class DatabaseService {
else {
reject(new Error('Unable to delete, document not found.'));
}
this.saveDatabase(+selectedNode.index);
this.saveDatabase(selectedNode, collectionName);
resolve(documentFieldValue);
}
catch (errRes) {
@ -126,17 +155,10 @@ export class DatabaseService {
}
});
}
validateDocument(collectionName, documentToValidate) {
switch (collectionName) {
case CollectionsEnum.OFFERS:
return validateOffer(documentToValidate);
default:
return ({ isValid: false, error: 'Collection does not exist' });
}
}
saveDatabase(nodeIndex) {
saveDatabase(selectedNode, collectionName) {
const nodeIndex = +selectedNode.index;
try {
if (+nodeIndex < 1) {
if (nodeIndex < 1) {
return true;
}
const selNode = this.nodeDatabase[nodeIndex] && this.nodeDatabase[nodeIndex].adapter && this.nodeDatabase[nodeIndex].adapter.selNode ? this.nodeDatabase[nodeIndex].adapter.selNode : null;
@ -144,69 +166,131 @@ export class DatabaseService {
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error: Selected Node Setup Not Found.' });
throw new Error('Database Save Error: Selected Node Setup Not Found.');
}
this.nodeDatabase[nodeIndex].adapter.saveData(this.nodeDatabase[nodeIndex].data);
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'INFO', fileName: 'Database', msg: 'Database Saved' });
this.nodeDatabase[nodeIndex].adapter.saveData(collectionName, this.nodeDatabase[selectedNode.index].data[collectionName]);
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'INFO', fileName: 'Database', msg: 'Database Collection ' + collectionName + ' Saved' });
return true;
}
catch (err) {
const selNode = this.nodeDatabase[nodeIndex] && this.nodeDatabase[nodeIndex].adapter && this.nodeDatabase[nodeIndex].adapter.selNode ? this.nodeDatabase[nodeIndex].adapter.selNode : null;
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error', error: err });
return new Error(err);
throw err;
}
}
unloadDatabase(nodeIndex, sessionID) {
if (nodeIndex > 0) {
if (this.nodeDatabase[nodeIndex] && this.nodeDatabase[nodeIndex].adapter) {
this.nodeDatabase[nodeIndex].adapter.removeSession(sessionID);
if (this.nodeDatabase[nodeIndex].adapter.userSessions && this.nodeDatabase[nodeIndex].adapter.userSessions.length <= 0) {
delete this.nodeDatabase[nodeIndex];
}
}
}
unloadDatabase(nodeIndex) {
this.saveDatabase(nodeIndex);
this.nodeDatabase[nodeIndex] = null;
}
}
export class DatabaseAdapter {
constructor(dbDirectoryPath, fileName, selNode = null) {
constructor(dbDirectoryPath, selNode = null, id = '') {
this.dbDirectoryPath = dbDirectoryPath;
this.fileName = fileName;
this.selNode = selNode;
this.dbFile = '';
this.dbFile = dbDirectoryPath + sep + fileName + '-node-' + selNode.index + '.json';
this.id = id;
this.logger = Logger;
this.common = Common;
this.dbFilePath = '';
this.userSessions = [];
this.dbFilePath = dbDirectoryPath + sep + 'node-' + selNode.index;
// For backward compatibility Start
const oldFilePath = dbDirectoryPath + sep + 'rtldb-node-' + selNode.index + '.json';
if (selNode.ln_implementation === 'CLN' && fs.existsSync(oldFilePath)) {
this.renameOldDB(oldFilePath, selNode);
}
fetchData() {
// For backward compatibility End
this.insertSession(id);
}
renameOldDB(oldFilePath, selNode = null) {
const newFilePath = this.dbFilePath + sep + 'rtldb-' + selNode.ln_implementation + '-Offers.json';
try {
if (!fs.existsSync(this.dbDirectoryPath)) {
fs.mkdirSync(this.dbDirectoryPath);
this.common.createDirectory(this.dbFilePath);
const oldOffers = JSON.parse(fs.readFileSync(oldFilePath, 'utf-8'));
fs.writeFileSync(oldFilePath, JSON.stringify(oldOffers.Offers, null, 2));
fs.renameSync(oldFilePath, newFilePath);
}
catch (err) {
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Rename Old Database Error', error: err });
}
}
fetchData(collectionName) {
try {
if (!fs.existsSync(this.dbFilePath)) {
this.common.createDirectory(this.dbFilePath);
}
}
catch (err) {
return new Error('Unable to Create Directory Error ' + JSON.stringify(err));
throw new Error(JSON.stringify(err));
}
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.ln_implementation + '-' + collectionName + '.json';
try {
if (!fs.existsSync(this.dbFile)) {
fs.writeFileSync(this.dbFile, '{}');
if (!fs.existsSync(collectionFilePath)) {
fs.writeFileSync(collectionFilePath, '[]');
}
}
catch (err) {
return new Error('Unable to Create Database File Error ' + JSON.stringify(err));
throw new Error(JSON.stringify(err));
}
try {
const dataFromFile = fs.readFileSync(this.dbFile, 'utf-8');
return !dataFromFile ? null : JSON.parse(dataFromFile);
const otherFiles = fs.readdirSync(this.dbFilePath);
otherFiles.forEach((oFileName) => {
let collectionValid = false;
switch (this.selNode.ln_implementation) {
case 'CLN':
collectionValid = CLNCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
break;
case 'ECL':
collectionValid = ECLCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
break;
default:
collectionValid = LNDCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
break;
}
if (oFileName.endsWith('.json') && !collectionValid) {
fs.renameSync(this.dbFilePath + sep + oFileName, this.dbFilePath + sep + oFileName + '.tmp');
}
});
}
catch (err) {
return new Error('Database Read Error ' + JSON.stringify(err));
this.logger.log({ selectedNode: this.selNode, level: 'ERROR', fileName: 'Database', msg: 'Rename Other Implementation DB Error', error: err });
}
try {
const dataFromFile = fs.readFileSync(collectionFilePath, 'utf-8');
const dataObj = !dataFromFile ? null : JSON.parse(dataFromFile);
return dataObj;
}
catch (err) {
throw new Error(JSON.stringify(err));
}
}
getSelNode() {
return this.selNode;
}
saveData(data) {
saveData(collectionName, collectionData) {
try {
if (data) {
const tempFile = this.dbFile + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(data, null, 2));
fs.renameSync(tempFile, this.dbFile);
if (collectionData) {
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.ln_implementation + '-' + collectionName + '.json';
const tempFile = collectionFilePath + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(collectionData, null, 2));
fs.renameSync(tempFile, collectionFilePath);
}
return true;
}
catch (err) {
return new Error('Database Write Error ' + JSON.stringify(err));
throw err;
}
}
insertSession(id = '') {
if (!this.userSessions.includes(id)) {
this.userSessions.push(id);
}
}
removeSession(sessionID = '') {
this.userSessions.splice(this.userSessions.findIndex((sId) => sId === sessionID), 1);
}
}
export const Database = new DatabaseService();

View File

@ -7,8 +7,10 @@ export class LoggerService {
switch (msgJSON.level) {
case 'ERROR':
if (msgJSON.error) {
msgStr = msgStr + ': ' + ((msgJSON.error.error && msgJSON.error.error.message && typeof msgJSON.error.error.message === 'string') ? msgJSON.error.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.message && typeof msgJSON.error.message === 'string') ? msgJSON.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.stack && typeof msgJSON.error.stack === 'string') ?
msgJSON.error.stack : (typeof msgJSON.error === 'object') ? JSON.stringify(msgJSON.error) : (typeof msgJSON.error === 'string') ? msgJSON.error : '') + '\r\n';
msgStr = msgStr + ': ' + ((msgJSON.error.error && msgJSON.error.error.message && typeof msgJSON.error.error.message === 'string') ?
msgJSON.error.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.message && typeof msgJSON.error.message === 'string') ? msgJSON.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.stack && typeof msgJSON.error.stack === 'string') ?
msgJSON.error.stack : (typeof msgJSON.error === 'object') ? JSON.stringify(msgJSON.error) : (typeof msgJSON.error === 'string') ?
msgJSON.error : '') + '\r\n';
}
else {
msgStr = msgStr + '.\r\n';
@ -67,7 +69,9 @@ export class LoggerService {
;
const prepMsgData = (msgJSON, msgStr) => {
if (msgJSON.data) {
msgStr = msgStr + ': ' + (typeof msgJSON.data === 'object' ? (msgJSON.data.message && typeof msgJSON.data.message === 'string') ? msgJSON.data.message : (msgJSON.data.stack && typeof msgJSON.data.stack === 'string') ? msgJSON.data.stack : JSON.stringify(msgJSON.data) : (typeof msgJSON.data === 'string') ? msgJSON.data : '') + '\r\n';
msgStr = msgStr + ': ' + (typeof msgJSON.data === 'object' ? (msgJSON.data.message && typeof msgJSON.data.message === 'string') ?
msgJSON.data.message : (msgJSON.data.stack && typeof msgJSON.data.stack === 'string') ?
msgJSON.data.stack : JSON.stringify(msgJSON.data) : (typeof msgJSON.data === 'string') ? msgJSON.data : '') + '\r\n';
}
else {
msgStr = msgStr + '.\r\n';

View File

@ -55,10 +55,10 @@ export class RTLWebSocketServer {
this.mountEventsOnConnection = (websocket, request) => {
var _a;
const protocols = !request.headers['sec-websocket-protocol'] ? [] : (_a = request.headers['sec-websocket-protocol'].split(',')) === null || _a === void 0 ? void 0 : _a.map((s) => s.trim());
const cookies = parse(request.headers.cookie);
const cookies = request.headers.cookie ? parse(request.headers.cookie) : null;
websocket.clientId = Date.now();
websocket.isAlive = true;
websocket.sessionId = cookieParser.signedCookie(cookies['connect.sid'], this.common.secret_key);
websocket.sessionId = cookies && cookies['connect.sid'] ? cookieParser.signedCookie(cookies['connect.sid'], this.common.secret_key) : null;
websocket.clientNodeIndex = +protocols[1];
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
websocket.on('error', this.sendErrorToAllLNClients);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -69,32 +69,6 @@ MIT
@angular/router
MIT
@babel/runtime
MIT
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@fortawesome/angular-fontawesome
MIT
MIT License
@ -2021,7 +1995,7 @@ MIT
The MIT License (MIT)
Copyright (c) 2014-2015 bpampuch
2016-2021 liborm85
2016-2022 liborm85
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@ -2204,31 +2178,6 @@ IN THE SOFTWARE.
"""
regenerator-runtime
MIT
MIT License
Copyright (c) 2014-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
resize-observer-polyfill
MIT
The MIT License (MIT)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,12 +3,12 @@
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"src": "/rtl/assets/images/favicon-dark/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"src": "/rtl/assets/images/favicon-dark/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}

View File

@ -3,12 +3,12 @@
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"src": "/rtl/assets/images/favicon-light/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"src": "/rtl/assets/images/favicon-light/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}

View File

@ -10,9 +10,9 @@
<link i18n-rel="" rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<meta i18n-content="" name="msapplication-TileColor" content="#da532c">
<meta i18n-content="" name="theme-color" content="#ffffff">
<style>@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:62.5%}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}</style><link rel="stylesheet" href="styles.43515fc39338348b.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.43515fc39338348b.css"></noscript></head>
<style>@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:62.5%}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}</style><link rel="stylesheet" href="styles.74a7770ce3bccfdd.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.74a7770ce3bccfdd.css"></noscript></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.1ca59cd92764ed45.js" type="module"></script><script src="polyfills.eddc63f1737a019a.js" type="module"></script><script src="main.9b30da1402d5f9f2.js" type="module"></script>
<script src="runtime.4c81b553a9df7303.js" type="module"></script><script src="polyfills.eddc63f1737a019a.js" type="module"></script><script src="main.41600bb9f8f7e3c5.js" type="module"></script>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(()=>{"use strict";var e,v={},g={};function r(e){var n=g[e];if(void 0!==n)return n.exports;var t=g[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(n,t,f,o)=>{if(!t){var a=1/0;for(i=0;i<e.length;i++){for(var[t,f,o]=e[i],c=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(c=!1,o<a&&(a=o));if(c){e.splice(i--,1);var d=f();void 0!==d&&(n=d)}}return n}o=o||0;for(var i=e.length;i>0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[t,f,o]},r.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return r.d(n,{a:n}),n},r.d=(e,n)=>{for(var t in n)r.o(n,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((n,t)=>(r.f[t](e,n),n),[])),r.u=e=>e+"."+{564:"3d38ee9330b2ba94",636:"95c8ae357b1ed820",893:"9a615c46b89a5a79",924:"1c1eb885f1f101d2"}[e]+".js",r.miniCssF=e=>{},r.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),(()=>{var e={},n="RTLApp:";r.l=(t,f,o,i)=>{if(e[t])e[t].push(f);else{var a,c;if(void 0!==o)for(var u=document.getElementsByTagName("script"),d=0;d<u.length;d++){var l=u[d];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==n+o){a=l;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",n+o),a.src=r.tu(t)),e[t]=[f];var s=(m,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(_=>_(b)),m)return m(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:n=>n},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var i=r.o(e,f)?e[f]:void 0;if(0!==i)if(i)o.push(i[2]);else if(666!=f){var a=new Promise((l,s)=>i=e[f]=[l,s]);o.push(i[2]=a);var c=r.p+r.u(f),u=new Error;r.l(c,l=>{if(r.o(e,f)&&(0!==(i=e[f])&&(e[f]=void 0),i)){var s=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;u.message="Loading chunk "+f+" failed.\n("+s+": "+p+")",u.name="ChunkLoadError",u.type=s,u.request=p,i[1](u)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var n=(f,o)=>{var u,d,[i,a,c]=o,l=0;if(i.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(c)var s=c(r)}for(f&&f(o);l<i.length;l++)r.o(e,d=i[l])&&e[d]&&e[d][0](),e[d]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(n.bind(null,0)),t.push=n.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},g={};function r(e){var n=g[e];if(void 0!==n)return n.exports;var t=g[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(n,t,f,o)=>{if(!t){var a=1/0;for(i=0;i<e.length;i++){for(var[t,f,o]=e[i],c=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(c=!1,o<a&&(a=o));if(c){e.splice(i--,1);var l=f();void 0!==l&&(n=l)}}return n}o=o||0;for(var i=e.length;i>0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[t,f,o]},r.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return r.d(n,{a:n}),n},r.d=(e,n)=>{for(var t in n)r.o(n,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((n,t)=>(r.f[t](e,n),n),[])),r.u=e=>e+"."+{258:"fb8729850462aa0e",267:"4a643eeda98f6031",564:"c60bd98c3a9d472b",636:"2c7ab7c33992b609"}[e]+".js",r.miniCssF=e=>{},r.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),(()=>{var e={},n="RTLApp:";r.l=(t,f,o,i)=>{if(e[t])e[t].push(f);else{var a,c;if(void 0!==o)for(var u=document.getElementsByTagName("script"),l=0;l<u.length;l++){var d=u[l];if(d.getAttribute("src")==t||d.getAttribute("data-webpack")==n+o){a=d;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",n+o),a.src=r.tu(t)),e[t]=[f];var s=(m,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(_=>_(b)),m)return m(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:n=>n},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var i=r.o(e,f)?e[f]:void 0;if(0!==i)if(i)o.push(i[2]);else if(666!=f){var a=new Promise((d,s)=>i=e[f]=[d,s]);o.push(i[2]=a);var c=r.p+r.u(f),u=new Error;r.l(c,d=>{if(r.o(e,f)&&(0!==(i=e[f])&&(e[f]=void 0),i)){var s=d&&("load"===d.type?"missing":d.type),p=d&&d.target&&d.target.src;u.message="Loading chunk "+f+" failed.\n("+s+": "+p+")",u.name="ChunkLoadError",u.type=s,u.request=p,i[1](u)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var n=(f,o)=>{var u,l,[i,a,c]=o,d=0;if(i.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(c)var s=c(r)}for(f&&f(o);d<i.length;d++)r.o(e,l=i[d])&&e[l]&&e[l][0](),e[l]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(n.bind(null,0)),t.push=n.bind(null,t.push.bind(t))})()})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3247
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "rtl",
"version": "0.13.1-beta",
"version": "0.13.2-beta",
"license": "MIT",
"type": "module",
"scripts": {
@ -12,8 +12,8 @@
"buildfrontend": "ng build --configuration production",
"buildbackend": "tsc --project tsconfig.json",
"watchbackend": "tsc --project tsconfig.json --watch",
"server": "set NODE_ENV=development&&nodemon ./rtl.js",
"serverUbuntu": "NODE_ENV=development nodemon ./rtl.js",
"server": "set NODE_ENV=development&&nodemon --watch backend --watch server ./rtl.js",
"serverUbuntu": "NODE_ENV=development nodemon --watch backend --watch server ./rtl.js",
"testdev": "ng test --watch=true --code-coverage",
"test": "ng test --watch=false",
"lint": "ng lint",
@ -21,21 +21,6 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~13.3.0",
"@angular/cdk": "^13.3.9",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/core": "~13.3.0",
"@angular/flex-layout": "^13.0.0-beta.38",
"@angular/forms": "~13.3.0",
"@angular/material": "^13.3.9",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"@fortawesome/angular-fontawesome": "^0.10.2",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@ngrx/effects": "^13.2.0",
"@ngrx/store": "^13.2.0",
"@swimlane/ngx-charts": "^20.1.0",
@ -48,17 +33,14 @@
"hocon-parser": "^1.0.1",
"ini": "^3.0.0",
"jsonwebtoken": "^8.5.1",
"material-design-icons": "^3.0.1",
"ng-qrcode": "^6.0.0",
"ngx-perfect-scrollbar-next": "^10.1.1",
"otplib": "^12.0.1",
"pdfmake": "^0.2.5",
"request-promise": "^4.2.6",
"roboto-fontface": "^0.10.0",
"rxjs": "^7.4.0",
"sha256": "^0.2.0",
"tslib": "^2.3.0",
"typescript": "~4.6.2",
"ws": "^8.8.1",
"zone.js": "~0.11.4"
},
@ -69,8 +51,23 @@
"@angular-eslint/eslint-plugin-template": "13.0.1",
"@angular-eslint/schematics": "13.0.1",
"@angular-eslint/template-parser": "13.0.1",
"@angular/animations": "~13.3.0",
"@angular/cdk": "^13.3.9",
"@angular/cli": "~13.3.5",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/compiler-cli": "~13.3.0",
"@angular/core": "~13.3.0",
"@angular/flex-layout": "^13.0.0-beta.38",
"@angular/forms": "~13.3.0",
"@angular/material": "^13.3.9",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"@fortawesome/angular-fontawesome": "^0.10.2",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@ngrx/store-devtools": "^13.0.2",
"@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1",
@ -87,9 +84,12 @@
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"material-design-icons": "^3.0.1",
"nodemon": "~2.0.19",
"protractor": "~7.0.0",
"roboto-fontface": "^0.10.0",
"stream-browserify": "^3.0.0",
"ts-node": "~10.9.1"
"ts-node": "~10.9.1",
"typescript": "~4.6.2"
}
}

View File

@ -56,7 +56,7 @@ export const getInfo = (req, res, next) => {
req.session.selectedNode.ln_version = body.version || '';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' });
clWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}

View File

@ -29,10 +29,6 @@ export const listInvoices = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List URL', data: options.url });
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
if (body.invoices && body.invoices.length > 0) {
body.invoices = common.sortDescByKey(body.invoices, 'expires_at');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Sorted Invoices List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Invoice', 'List Invoices Error', req.session.selectedNode);

View File

@ -72,8 +72,10 @@ export const listNodes = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
body.forEach((node) => {
if (node.option_will_fund) {
node.option_will_fund.lease_fee_base_msat = (node.option_will_fund.lease_fee_base_msat && typeof node.option_will_fund.lease_fee_base_msat === 'string' && node.option_will_fund.lease_fee_base_msat.includes('msat')) ? node.option_will_fund.lease_fee_base_msat?.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.channel_fee_max_base_msat = (node.option_will_fund.channel_fee_max_base_msat && typeof node.option_will_fund.channel_fee_max_base_msat === 'string' && node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? node.option_will_fund.channel_fee_max_base_msat?.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
node.option_will_fund.lease_fee_base_msat = (node.option_will_fund.lease_fee_base_msat && typeof node.option_will_fund.lease_fee_base_msat === 'string' &&
node.option_will_fund.lease_fee_base_msat.includes('msat')) ? node.option_will_fund.lease_fee_base_msat?.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.channel_fee_max_base_msat = (node.option_will_fund.channel_fee_max_base_msat && typeof node.option_will_fund.channel_fee_max_base_msat === 'string' &&
node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? node.option_will_fund.channel_fee_max_base_msat?.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
}
return node;
});

View File

@ -11,11 +11,8 @@ const databaseService: DatabaseService = Database;
export const listOfferBookmarks = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Bookmarks..' });
databaseService.find(req.session.selectedNode, CollectionsEnum.OFFERS).then((offers: Offer[]) => {
databaseService.find(req.session.selectedNode, CollectionsEnum.OFFERS).then((offers: any) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmarks Received', data: offers });
if (offers && offers.length > 0) {
offers = common.sortDescByKey(offers, 'lastUpdatedAt');
}
res.status(200).json(offers);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Offers', 'Offer Bookmarks Error', req.session.selectedNode);
@ -25,7 +22,7 @@ export const listOfferBookmarks = (req, res, next) => {
export const deleteOfferBookmark = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' });
databaseService.destroy(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, req.params.offerStr).then((deleteRes) => {
databaseService.remove(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, req.params.offerStr).then((deleteRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: deleteRes });
res.status(204).json(req.params.offerStr);
}).catch((errRes) => {

View File

@ -41,7 +41,6 @@ export const getUTXOs = (req, res, next) => {
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/listFunds';
request(options).then((body) => {
if (body.outputs) { body.outputs = common.sortDescByStrKey(body.outputs, 'status'); }
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Funds List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {

View File

@ -63,10 +63,6 @@ export const listPayments = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/pay/listPayments';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body.payments });
if (body && body.payments && body.payments.length > 0) {
body.payments = common.sortDescByKey(body.payments, 'created_at');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sorted Payments List Received', data: body.payments });
res.status(200).json(groupBy(body.payments));
}).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);
@ -95,16 +91,22 @@ export const postPayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body });
if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB && req.body.bolt12) {
const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountmSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.vendor) { offerToUpdate['vendor'] = req.body.vendor; }
if (req.body.description) { offerToUpdate['description'] = req.body.description; }
// eslint-disable-next-line arrow-body-style
return databaseService.validateDocument(CollectionsEnum.OFFERS, offerToUpdate).then((validated) => {
return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, req.body.bolt12).then((updatedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Updated', data: updatedOffer });
logger.log({ level: 'DEBUG', fileName: 'Payments', msg: 'Offer Updated', data: updatedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer });
}).catch((errDB) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB });
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
});
}).catch((errValidation) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB validation error', error: errValidation });
return res.status(201).json({ paymentResponse: body, saveToDBError: errValidation });
});
} else {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}

View File

@ -16,9 +16,8 @@ export const getPeers = (req, res, next) => {
peer.alias = peer.id.substring(0, 20);
}
});
const peers = (body) ? common.sortDescByStrKey(body, 'alias') : [];
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers with Alias Received', data: peers });
res.status(200).json(peers);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers with Alias Received', data: body });
res.status(200).json(body || []);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -31,12 +30,11 @@ export const postPeer = (req, res, next) => {
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/connect';
options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: body });
request.post(options).then((connectRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: connectRes });
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/listPeers';
request(options).then((body) => {
let peers = (body) ? common.sortDescByStrKey(body, 'alias') : [];
peers = common.newestOnTop(peers, 'id', req.body.id);
request(options).then((listPeersRes) => {
const peers = listPeersRes ? common.newestOnTop(listPeersRes, 'id', req.body.id) : [];
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
res.status(201).json(peers);
}).catch((errRes) => {

View File

@ -15,10 +15,10 @@ export const simplifyAllChannels = (selNode: CommonSelectedNode, channels) => {
nodeId: channel.nodeId ? channel.nodeId : '',
channelId: channel.channelId ? channel.channelId : '',
state: channel.state ? channel.state : '',
channelFlags: channel.data && channel.data.commitments && channel.data.commitments.channelFlags ? channel.data.commitments.channelFlags : 0,
announceChannel: channel.data && channel.data.commitments && channel.data.commitments.channelFlags && channel.data.commitments.channelFlags.announceChannel ? channel.data.commitments.channelFlags.announceChannel : false,
toLocal: (channel.data.commitments.localCommit.spec.toLocal) ? Math.round(+channel.data.commitments.localCommit.spec.toLocal / 1000) : 0,
toRemote: (channel.data.commitments.localCommit.spec.toRemote) ? Math.round(+channel.data.commitments.localCommit.spec.toRemote / 1000) : 0,
shortChannelId: channel.data && channel.data.shortChannelId ? channel.data.shortChannelId : '',
shortChannelId: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.shortChannelId ? channel.data.channelUpdate.shortChannelId : '',
isFunder: channel.data && channel.data.commitments && channel.data.commitments.localParams && channel.data.commitments.localParams.isFunder ? channel.data.commitments.localParams.isFunder : false,
buried: channel.data && channel.data.buried ? channel.data.buried : false,
feeBaseMsat: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.feeBaseMsat ? channel.data.channelUpdate.feeBaseMsat : 0,

View File

@ -72,9 +72,6 @@ export const arrangePayments = (selNode: CommonSelectedNode, body) => {
if (relayedEle.amountIn) { relayedEle.amountIn = Math.round(relayedEle.amountIn / 1000); }
if (relayedEle.amountOut) { relayedEle.amountOut = Math.round(relayedEle.amountOut / 1000); }
});
payments.sent = common.sortDescByKey(payments.sent, 'firstPartTimestamp');
payments.received = common.sortDescByKey(payments.received, 'firstPartTimestamp');
payments.relayed = common.sortDescByKey(payments.relayed, 'timestamp');
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Fees', msg: 'Arranged Payments Received', data: payments });
return payments;
};

View File

@ -36,11 +36,11 @@ export const getInfo = (req, res, next) => {
body.lnImplementation = 'Eclair';
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
eclWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req);
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
}

View File

@ -67,10 +67,7 @@ export const listInvoices = (req, res, next) => {
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];
pendingInvoices = (!body[1] || body[1].length <= 0) ? [] : body[1];
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => {
body = common.sortDescByKey(invoices, 'expiresAt');
return res.status(200).json(invoices);
});
then((values) => res.status(200).json(invoices));
});
} else {
return Promise.all([request(options1), request(options2)]).
@ -81,7 +78,6 @@ export const listInvoices = (req, res, next) => {
if (invoices && invoices.length > 0) {
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => {
body = common.sortDescByKey(invoices, 'expiresAt');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Sorted Invoices List Received', data: invoices });
return res.status(200).json(invoices);
}).

View File

@ -59,7 +59,6 @@ export const getTransactions = (req, res, next) => {
};
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Getting On Chain Transactions Options', data: options.form });
request.post(options).then((body) => {
if (body && body.length > 0) { body = common.sortDescByKey(body, 'timestamp'); }
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'On Chain Transactions Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {

View File

@ -37,7 +37,6 @@ export const getPeers = (req, res, next) => {
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
return peer;
});
body = common.sortDescByStrKey(body, 'alias');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body });
res.status(200).json(body);
});
@ -87,8 +86,7 @@ export const connectPeer = (req, res, next) => {
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
return peer;
});
let peers = (body) ? common.sortDescByStrKey(body, 'alias') : [];
peers = common.newestOnTop(peers, 'nodeId', req.query.nodeId ? req.query.nodeId : req.query.uri ? req.query.uri.substring(0, req.query.uri.indexOf('@')) : '');
const peers = common.newestOnTop(body || [], 'nodeId', req.query.nodeId ? req.query.nodeId : req.query.uri ? req.query.uri.substring(0, req.query.uri.indexOf('@')) : '');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
res.status(201).json(peers);
});

View File

@ -11,11 +11,11 @@ export const getAliasForChannel = (selNode: CommonSelectedNode, channel) => {
options.url = selNode.ln_server_url + '/v1/graph/node/' + pubkey;
return request(options).then((aliasBody) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Alias Received', data: aliasBody.node.alias });
channel.remote_alias = aliasBody.node.alias;
return aliasBody.node.alias;
channel.remote_alias = aliasBody.node.alias && aliasBody.node.alias !== '' ? aliasBody.node.alias : aliasBody.node.pub_key.slice(0, 20);
return channel;
}).catch((err) => {
channel.remote_alias = pubkey.slice(0, 10) + '...' + pubkey.slice(-10);
return pubkey;
channel.remote_alias = pubkey.slice(0, 20);
return channel;
});
};
@ -40,7 +40,6 @@ export const getAllChannels = (req, res, next) => {
return getAliasForChannel(req.session.selectedNode, channel);
})
).then((values) => {
body.channels = common.sortDescByKey(body.channels, 'balancedness');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sorted Channels List Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {
@ -72,12 +71,12 @@ export const getPendingChannels = (req, res, next) => {
if (body.pending_open_channels && body.pending_open_channels.length > 0) {
body.pending_open_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_closing_channels && body.pending_closing_channels.length > 0) {
body.pending_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_force_closing_channels && body.pending_force_closing_channels.length > 0) {
body.pending_force_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_closing_channels && body.pending_closing_channels.length > 0) {
body.pending_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.waiting_close_channels && body.waiting_close_channels.length > 0) {
body.waiting_close_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
@ -109,7 +108,6 @@ export const getClosedChannels = (req, res, next) => {
return getAliasForChannel(req.session.selectedNode, channel);
})
).then((values) => {
body.channels = common.sortDescByKey(body.channels, 'close_height');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closed Channels List Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {
@ -156,7 +154,7 @@ export const postTransactions = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sending Payment..' });
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/transactions';
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/transaction-stream';
options.form = { payment_request: req.body.paymentReq };
if (req.body.paymentAmount) {
options.form.amt = req.body.paymentAmount;

View File

@ -40,7 +40,7 @@ export const getInfo = (req, res, next) => {
} else {
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
lndWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}

View File

@ -12,7 +12,7 @@ export const getAliasFromPubkey = (selNode: CommonSelectedNode, pubkey) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Graph', msg: 'Alias Received', data: res.node.alias });
return res.node.alias;
}).
catch((err) => pubkey.substring(0, 17) + '...');
catch((err) => pubkey.substring(0, 20));
};
export const getDescribeGraph = (req, res, next) => {

View File

@ -44,7 +44,6 @@ export const listInvoices = (req, res, next) => {
invoice.r_hash = invoice.r_hash ? Buffer.from(invoice.r_hash, 'base64').toString('hex') : '';
invoice.description_hash = invoice.description_hash ? Buffer.from(invoice.description_hash, 'base64').toString('hex') : null;
});
body.invoices = common.sortDescByKey(body.invoices, 'creation_date');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Sorted Invoices List Received', data: body });
res.status(200).json(body);
@ -59,17 +58,7 @@ export const addInvoice = (req, res, next) => {
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/invoices';
options.form = {
memo: req.body.memo,
private: req.body.private,
expiry: req.body.expiry
};
if (req.body.amount > 0 && req.body.amount < 1) {
options.form.value_msat = req.body.amount * 1000;
} else {
options.form.value = req.body.amount;
}
options.form = JSON.stringify(options.form);
options.form = JSON.stringify(req.body);
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Added', data: body });
try {

View File

@ -57,10 +57,6 @@ export const getPayments = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=' + req.query.max_payments + '&index_offset=' + req.query.index_offset + '&reversed=' + req.query.reversed;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body });
if (body.payments && body.payments.length > 0) {
body.payments = common.sortDescByKey(body.payments, 'creation_date');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sorted Payments List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);

View File

@ -13,7 +13,7 @@ export const getAliasForPeers = (selNode: CommonSelectedNode, peer) => {
peer.alias = aliasBody.node.alias;
return aliasBody.node.alias;
}).catch((err) => {
peer.alias = peer.pub_key.slice(0, 10) + '...' + peer.pub_key.slice(-10);
peer.alias = peer.pub_key.slice(0, 20);
return peer.pub_key;
});
};
@ -27,10 +27,6 @@ export const getPeers = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
const peers = !body.peers ? [] : body.peers;
return Promise.all(peers?.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers with Alias before Sort', data: body });
if (body.peers) {
body.peers = common.sortDescByStrKey(body.peers, 'alias');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body.peers });
res.status(200).json(body.peers);
});
@ -56,7 +52,6 @@ export const postPeer = (req, res, next) => {
const peers = (!body.peers) ? [] : body.peers;
return Promise.all(peers?.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
if (body.peers) {
body.peers = common.sortDescByStrKey(body.peers, 'alias');
body.peers = common.newestOnTop(body.peers, 'pub_key', req.body.pubkey);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: body });
}

View File

@ -40,9 +40,6 @@ export const getAllForwardingEvents = (req, start, end, offset, caller, callback
}
if (!body.last_offset_index || body.last_offset_index < offset + num_max_events) {
responseData[caller].last_offset_index = body.last_offset_index ? body.last_offset_index : 0;
if (responseData[caller].forwarding_events) {
responseData[caller].forwarding_events = common.sortDescByKey(responseData[caller].forwarding_events, 'timestamp');
}
return callback(responseData[caller]);
} else {
return getAllForwardingEvents(req, start, end, offset + num_max_events, caller, callback);

View File

@ -12,10 +12,6 @@ export const getTransactions = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/transactions';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Transactions', msg: 'Transactions List Received', data: body });
if (body.transactions && body.transactions.length > 0) {
body.transactions = common.sortDescByKey(body.transactions, 'time_stamp');
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Transactions', msg: 'Sorted Transactions List Received', data: body.transactions });
res.status(200).json(body.transactions);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Transactions', 'List Transactions Error', req.session.selectedNode);

View File

@ -22,7 +22,7 @@ export const updateSelectedNode = (req, res, next) => {
if (req.headers && req.headers.authorization && req.headers.authorization !== '') {
wsServer.updateLNWSClientDetails(req.session.id, +req.session.selectedNode.index, +req.params.prevNodeIndex);
if (req.params.prevNodeIndex !== -1) {
databaseService.unloadDatabase(req.params.prevNodeIndex);
databaseService.unloadDatabase(req.params.prevNodeIndex, req.session.id);
}
}
const responseVal = !req.session.selectedNode.ln_node ? '' : req.session.selectedNode.ln_node;
@ -51,10 +51,11 @@ export const getRTLConfigInitial = (req, res, next) => {
const nodesArr = [];
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => {
const settings: NodeSettingsConfiguration = {};
const settings: NodeSettingsConfiguration = { unannouncedChannels: false };
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.unannouncedChannels = !!node.unannounced_channels || false;
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
@ -98,10 +99,11 @@ export const getRTLConfig = (req, res, next) => {
authentication.configPath = (node.config_path) ? node.config_path : '';
authentication.swapMacaroonPath = (node.swap_macaroon_path) ? node.swap_macaroon_path : '';
authentication.boltzMacaroonPath = (node.boltz_macaroon_path) ? node.boltz_macaroon_path : '';
const settings: NodeSettingsConfiguration = {};
const settings: NodeSettingsConfiguration = { unannouncedChannels: false };
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.unannouncedChannels = !!node.unannounced_channels || false;
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.bitcoindConfigPath = node.bitcoind_config_path;
settings.logLevel = node.log_level ? node.log_level : 'ERROR';
@ -109,6 +111,7 @@ export const getRTLConfig = (req, res, next) => {
settings.swapServerUrl = node.swap_server_url;
settings.boltzServerUrl = node.boltz_server_url;
settings.enableOffers = node.enable_offers;
settings.enablePeerswap = node.enable_peerswap;
settings.channelBackupPath = node.channel_backup_path;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
@ -136,6 +139,7 @@ export const updateUISettings = (req, res, next) => {
node.Settings.userPersona = req.body.updatedSettings.userPersona;
node.Settings.themeMode = req.body.updatedSettings.themeMode;
node.Settings.themeColor = req.body.updatedSettings.themeColor;
node.Settings.unannouncedChannels = req.body.updatedSettings.unannouncedChannels;
node.Settings.fiatConversion = req.body.updatedSettings.fiatConversion;
if (req.body.updatedSettings.fiatConversion) {
node.Settings.currencyUnit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
@ -146,6 +150,7 @@ export const updateUISettings = (req, res, next) => {
selectedNode.user_persona = req.body.updatedSettings.userPersona;
selectedNode.theme_mode = req.body.updatedSettings.themeMode;
selectedNode.theme_color = req.body.updatedSettings.themeColor;
selectedNode.unannounced_channels = req.body.updatedSettings.unannouncedChannels;
selectedNode.fiat_conversion = req.body.updatedSettings.fiatConversion;
if (req.body.updatedSettings.fiatConversion) {
selectedNode.currency_unit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
@ -240,7 +245,7 @@ export const getConfig = (req, res, next) => {
if (jsonConfig['Application Options'] && jsonConfig['Application Options'].color) {
jsonConfig['Application Options'].color = '#' + jsonConfig['Application Options'].color;
}
if (req.session.selectedNode.ln_implementation === 'ECL' && !jsonConfig['eclair.api.password']) {
if (req.params.nodeType === 'ln' && req.session.selectedNode.ln_implementation === 'ECL' && !jsonConfig['eclair.api.password']) {
fileFormat = 'HOCON';
jsonConfig = parseHocon(data);
}
@ -306,7 +311,7 @@ export const updateServiceSettings = (req, res, next) => {
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const selectedNode = common.findNode(req.session.selectedNode.index);
config.nodes.find((node) => {
config.nodes.forEach((node) => {
if (node.index === req.session.selectedNode.index) {
switch (req.body.service) {
case 'LOOP':
@ -342,6 +347,11 @@ export const updateServiceSettings = (req, res, next) => {
selectedNode.enable_offers = req.body.settings.enableOffers;
break;
case 'PEERSWAP':
node.Settings.enablePeerswap = req.body.settings.enablePeerswap;
selectedNode.enable_peerswap = req.body.settings.enablePeerswap;
break;
default:
break;
}
@ -370,7 +380,8 @@ export const maskPasswords = (obj) => {
}
if (typeof keys[i] === 'string' &&
(keys[i].toLowerCase().includes('password') || keys[i].toLowerCase().includes('multipass') ||
keys[i].toLowerCase().includes('rpcpass') || keys[i].toLowerCase().includes('rpcpassword'))
keys[i].toLowerCase().includes('rpcpass') || keys[i].toLowerCase().includes('rpcpassword') ||
keys[i].toLowerCase().includes('rpcuser'))
) {
obj[keys[i]] = '********************';
}

View File

@ -120,7 +120,7 @@ export const resetPassword = (req, res, next) => {
export const logoutUser = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Logged out' });
if (req.session.selectedNode && req.session.selectedNode.index) {
databaseService.unloadDatabase(+req.session.selectedNode.index);
databaseService.unloadDatabase(+req.session.selectedNode.index, req.session.id);
}
req.session.destroy((err) => {
res.clearCookie('connect.sid');

View File

@ -219,10 +219,6 @@ export const swaps = (req, res, next) => {
options.url = options.url + '/v1/loop/swaps';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Swaps Received', data: body });
if (body.swaps && body.swaps.length > 0) {
body.swaps = common.sortDescByKey(body.swaps, 'initiation_time');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Sorted Loop Swaps List Received', data: body });
}
res.status(200).json(body.swaps);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Loop', 'List Swaps Error', req.session.selectedNode);

View File

@ -0,0 +1,36 @@
import { Database, DatabaseService } from '../../utils/database.js';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { CollectionsEnum, PageSettings } from '../../models/database.model.js';
const logger: LoggerService = Logger;
const common: CommonService = Common;
const databaseService: DatabaseService = Database;
export const getPageSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Getting Page Settings..' });
databaseService.find(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS).then((settings: PageSettings) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Received', data: settings });
res.status(200).json(settings);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Page Settings', 'Page Settings Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const savePageSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Saving Page Settings..' });
// eslint-disable-next-line arrow-body-style
return Promise.all(req.body.map((page) => databaseService.validateDocument(CollectionsEnum.PAGE_SETTINGS, page))).then((values) => {
return databaseService.insert(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS, req.body).then((insertRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Updated', data: insertRes });
res.status(201).json(insertRes);
}).catch((insertErrRes) => {
const err = common.handleError(insertErrRes, 'Page Settings', 'Page Settings Update Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
}).catch((errRes) => {
const err = common.handleError(errRes, 'Page Settings', 'Page Settings Validation Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

View File

@ -21,11 +21,13 @@ export class CommonSelectedNode {
public user_persona?: string,
public theme_mode?: string,
public theme_color?: string,
public unannounced_channels?: boolean,
public fiat_conversion?: boolean,
public currency_unit?: string,
public ln_version?: string,
public api_version?: string,
public enable_offers?: boolean
public enable_offers?: boolean,
public enable_peerswap?: boolean
) { }
}
@ -46,6 +48,7 @@ export class NodeSettingsConfiguration {
public userPersona?: string,
public themeMode?: string,
public themeColor?: string,
public unannouncedChannels?: boolean,
public fiatConversion?: boolean,
public currencyUnit?: string,
public bitcoindConfigPath?: string,
@ -54,7 +57,8 @@ export class NodeSettingsConfiguration {
public swapServerUrl?: string,
public boltzServerUrl?: string,
public channelBackupPath?: string,
public enableOffers?: boolean
public enableOffers?: boolean,
public enablePeerswap?: boolean
) { }
}

View File

@ -1,26 +1,16 @@
export enum CollectionsEnum {
OFFERS = 'Offers'
}
export type Collections = {
Offers: Offer[];
}
export enum OfferFieldsEnum {
BOLT12 = 'bolt12',
AMOUNTMSAT = 'amountmSat',
AMOUNTMSAT = 'amountMSat',
TITLE = 'title',
VENDOR = 'vendor',
DESCRIPTION = 'description'
}
export const CollectionFieldsEnum = { ...OfferFieldsEnum };
export class Offer {
constructor(
public bolt12: string,
public amountmSat: number,
public amountMSat: number,
public title: string,
public vendor?: string,
public description?: string,
@ -29,18 +19,138 @@ export class Offer {
}
export const validateDocument = (collectionName: CollectionsEnum, documentToValidate: any): any => {
switch (collectionName) {
case CollectionsEnum.OFFERS:
return validateOffer(documentToValidate);
case CollectionsEnum.PAGE_SETTINGS:
return validatePageSettings(documentToValidate);
default:
return ({ isValid: false, error: 'Collection does not exist' });
}
};
export const validateOffer = (documentToValidate): any => {
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.BOLT12)) {
return ({ isValid: false, error: CollectionFieldsEnum.BOLT12 + 'is mandatory.' });
return ({ isValid: false, error: 'Bolt12 is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.AMOUNTMSAT)) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'is mandatory.' });
return ({ isValid: false, error: 'Amount mSat is mandatory.' });
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TITLE)) {
return ({ isValid: false, error: CollectionFieldsEnum.TITLE + 'is mandatory.' });
return ({ isValid: false, error: 'Title is mandatory.' });
}
if ((typeof documentToValidate[CollectionFieldsEnum.AMOUNTMSAT] !== 'number')) {
return ({ isValid: false, error: CollectionFieldsEnum.AMOUNTMSAT + 'should be a number.' });
return ({ isValid: false, error: 'Amount mSat should be a number.' });
}
return ({ isValid: true });
};
export enum SortOrderEnum {
ASCENDING = 'asc',
DESCENDING = 'desc'
}
export enum PageSettingsFieldsEnum {
PAGE_ID = 'pageId',
TABLES = 'tables'
}
export enum TableSettingsFieldsEnum {
TABLE_ID = 'tableId',
RECORDS_PER_PAGE = 'recordsPerPage',
SORT_BY = 'sortBy',
SORT_ORDER = 'sortOrder',
COLUMN_SELECTION = 'columnSelection',
COLUMN_SELECTION_SM = 'columnSelectionSM'
}
export class TableSetting {
constructor(
public tableId: string,
public recordsPerPage?: number,
public sortBy?: string,
public sortOrder?: SortOrderEnum,
public columnSelection?: any[]
) { }
}
export class PageSettings {
constructor(
public pageId: string,
public tables: TableSetting[]
) { }
}
export const validatePageSettings = (documentToValidate): any => {
let errorMessages = '';
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.PAGE_ID)) {
errorMessages = errorMessages + 'Page ID is mandatory.';
}
if (!documentToValidate.hasOwnProperty(CollectionFieldsEnum.TABLES)) {
errorMessages = errorMessages + 'Tables is mandatory.';
}
const tablesMessages = documentToValidate.tables.reduce((tableAcc, table: TableSetting, tableIdx) => {
let errMsg = '';
if (!table.hasOwnProperty(CollectionFieldsEnum.TABLE_ID)) {
errMsg = errMsg + 'Table ID is mandatory.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.SORT_BY)) {
errMsg = errMsg + 'Sort By is mandatory.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.SORT_ORDER)) {
errMsg = errMsg + 'Sort Order is mandatory.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.COLUMN_SELECTION_SM)) {
errMsg = errMsg + 'Column Selection (Mobile Resolution) is mandatory.';
}
if (table[CollectionFieldsEnum.COLUMN_SELECTION_SM].length < 1) {
errMsg = errMsg + 'Column Selection (Mobile Resolution) should have at least 1 field.';
}
if (table[CollectionFieldsEnum.COLUMN_SELECTION_SM].length > 3) {
errMsg = errMsg + 'Column Selection (Mobile Resolution) should have maximum 3 fields.';
}
if (!table.hasOwnProperty(CollectionFieldsEnum.COLUMN_SELECTION)) {
errMsg = errMsg + 'Column Selection (Desktop Resolution) is mandatory.';
}
if (table[CollectionFieldsEnum.COLUMN_SELECTION].length < 2) {
errMsg = errMsg + 'Column Selection (Desktop Resolution) should have at least 2 fields.';
}
if (errMsg.trim() !== '') {
tableAcc.push({ table: (table.hasOwnProperty(CollectionFieldsEnum.TABLE_ID) ? table[CollectionFieldsEnum.TABLE_ID] : (tableIdx + 1)), message: errMsg });
}
return tableAcc;
}, []);
if (errorMessages.trim() === '' && tablesMessages.length === 0) {
return ({ isValid: true });
} else {
const errObj = { page: (documentToValidate.hasOwnProperty(CollectionFieldsEnum.PAGE_ID) ? documentToValidate[CollectionFieldsEnum.PAGE_ID] : 'Unknown') };
if (errorMessages.trim() !== '') {
errObj['message'] = errorMessages;
}
if (tablesMessages.length && tablesMessages.length > 0) {
errObj['tables'] = tablesMessages;
}
return ({ isValid: false, error: JSON.stringify(errObj) });
}
};
export enum CollectionsEnum {
OFFERS = 'Offers',
PAGE_SETTINGS = 'PageSettings'
}
export type Collections = {
Offers: Offer[];
PageSettings: PageSettings[];
}
export const CollectionFieldsEnum = { ...OfferFieldsEnum, ...PageSettingsFieldsEnum, ...TableSettingsFieldsEnum };
export const LNDCollection = [CollectionsEnum.PAGE_SETTINGS];
export const ECLCollection = [CollectionsEnum.PAGE_SETTINGS];
export const CLNCollection = [CollectionsEnum.PAGE_SETTINGS, CollectionsEnum.OFFERS];

View File

@ -4,6 +4,7 @@ import authenticateRoutes from './authenticate.js';
import boltzRoutes from './boltz.js';
import loopRoutes from './loop.js';
import RTLConfRoutes from './RTLConf.js';
import pageSettingsRoutes from './pageSettings.js';
const router = Router();
@ -11,7 +12,8 @@ const sharedRoutes = [
{ path: '/authenticate', route: authenticateRoutes },
{ path: '/boltz', route: boltzRoutes },
{ path: '/loop', route: loopRoutes },
{ path: '/conf', route: RTLConfRoutes }
{ path: '/conf', route: RTLConfRoutes },
{ path: '/pagesettings', route: pageSettingsRoutes }
];
sharedRoutes.forEach((route) => {

View File

@ -0,0 +1,11 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getPageSettings, savePageSettings } from '../../controllers/shared/pageSettings.js';
const router = Router();
router.get('/', isAuthenticated, getPageSettings);
router.post('/', isAuthenticated, savePageSettings);
export default router;

View File

@ -63,13 +63,14 @@ export class ExpressApplication {
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
this.app.use((req: any, res, next) => {
// For Angular App
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '');
// For JQuery Browser Plugin
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '');
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Quickpay JQuery
res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html'));
});
this.app.use((err, req, res, next) => this.handleApplicationErrors(err, res));
this.app.use((err, req, res, next) => {
this.handleApplicationErrors(err, res);
next();
});
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' });
};

View File

@ -26,7 +26,10 @@ export class CommonService {
public read_dummy_data = false;
public baseHref = '/rtl';
private dummy_data_array_from_file = [];
private MONTHS = [{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 }, { name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }];
private MONTHS = [
{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 },
{ name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }
];
constructor() { }
@ -268,7 +271,15 @@ export class CommonService {
break;
}
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') });
const newErrorObj = {
let newErrorObj = { statusCode: 500, message: '', error: '' };
if (err.code && err.code === 'ENOENT') {
newErrorObj = {
statusCode: 500,
message: 'No such file or directory ' + (err.path ? err.path : ''),
error: 'No such file or directory ' + (err.path ? err.path : '')
};
} else {
newErrorObj = {
statusCode: err.statusCode ? err.statusCode : err.status ? err.status : (err.error && err.error.code && err.error.code === 'ECONNREFUSED') ? 503 : 500,
message: (err.error && err.error.message) ? err.error.message : err.message ? err.message : errMsg,
error: (
@ -280,6 +291,8 @@ export class CommonService {
(err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error'
)
};
}
if (selectedNode.ln_implementation === 'ECL' && err.message.indexOf('Authentication Error') < 0 && err.name === 'StatusCodeError') { newErrorObj.statusCode = 500; }
return newErrorObj;
};

View File

@ -70,12 +70,13 @@ export class ConfigService {
channelBackupPath: channelBackupPath,
logLevel: 'ERROR',
lnServerUrl: 'https://localhost:8080',
fiatConversion: false
fiatConversion: false,
unannouncedChannels: false
}
}
]
};
if (+process.env.RTL_SSO === 0) {
if (+process.env.RTL_SSO === 0 || configData.SSO.rtlSSO === 0) {
configData['multiPass'] = 'password';
}
return configData;
@ -200,6 +201,7 @@ export class ConfigService {
this.common.nodes[idx].user_persona = node.Settings.userPersona ? node.Settings.userPersona : 'MERCHANT';
this.common.nodes[idx].theme_mode = node.Settings.themeMode ? node.Settings.themeMode : 'DAY';
this.common.nodes[idx].theme_color = node.Settings.themeColor ? node.Settings.themeColor : 'PURPLE';
this.common.nodes[idx].unannounced_channels = node.Settings.unannouncedChannels ? !!node.Settings.unannouncedChannels : false;
this.common.nodes[idx].log_level = node.Settings.logLevel ? node.Settings.logLevel : 'ERROR';
this.common.nodes[idx].fiat_conversion = node.Settings.fiatConversion ? !!node.Settings.fiatConversion : false;
if (this.common.nodes[idx].fiat_conversion) {
@ -226,6 +228,7 @@ export class ConfigService {
this.common.nodes[idx].boltz_macaroon_path = '';
}
this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS ? process.env.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
this.common.nodes[idx].enable_peerswap = process.env.ENABLE_PEERSWAP ? process.env.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
try {

View File

@ -3,7 +3,7 @@ import { join, dirname, sep } from 'path';
import { fileURLToPath } from 'url';
import { Common, CommonService } from '../utils/common.js';
import { Logger, LoggerService } from '../utils/logger.js';
import { Collections, CollectionsEnum, validateOffer } from '../models/database.model.js';
import { Collections, CollectionsEnum, validateDocument, LNDCollection, ECLCollection, CLNCollection } from '../models/database.model.js';
import { CommonSelectedNode } from '../models/config.model.js';
export class DatabaseService {
@ -15,32 +15,70 @@ export class DatabaseService {
constructor() { }
loadDatabase(selectedNode: CommonSelectedNode) {
loadDatabase(session: any) {
const { id, selectedNode } = session;
try {
if (!this.nodeDatabase[selectedNode.index]) {
this.nodeDatabase[selectedNode.index] = { adapter: null, data: null };
this.nodeDatabase[selectedNode.index] = { adapter: null, data: {} };
this.nodeDatabase[selectedNode.index].adapter = new DatabaseAdapter(this.dbDirectory, selectedNode, id);
this.fetchNodeData(selectedNode);
this.logger.log({ selectedNode: selectedNode, level: 'DEBUG', fileName: 'Database', msg: 'Database Loaded', data: this.nodeDatabase[selectedNode.index].data });
} else {
this.nodeDatabase[selectedNode.index].adapter.insertSession(id);
}
this.nodeDatabase[selectedNode.index].adapter = new DatabaseAdapter(this.dbDirectory, 'rtldb', selectedNode);
this.nodeDatabase[selectedNode.index].data = this.nodeDatabase[selectedNode.index].adapter.fetchData();
} catch (err) {
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: 'Database', msg: 'Database Load Error', error: err });
}
}
create(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, newDocument: any) {
fetchNodeData(selectedNode: CommonSelectedNode) {
switch (selectedNode.ln_implementation) {
case 'CLN':
for (const collectionName in CLNCollection) {
if (CLNCollection.hasOwnProperty(collectionName)) {
this.nodeDatabase[selectedNode.index].data[CLNCollection[collectionName]] = this.nodeDatabase[selectedNode.index].adapter.fetchData(CLNCollection[collectionName]);
}
}
break;
case 'ECL':
for (const collectionName in ECLCollection) {
if (ECLCollection.hasOwnProperty(collectionName)) {
this.nodeDatabase[selectedNode.index].data[ECLCollection[collectionName]] = this.nodeDatabase[selectedNode.index].adapter.fetchData(ECLCollection[collectionName]);
}
}
break;
default:
for (const collectionName in LNDCollection) {
if (LNDCollection.hasOwnProperty(collectionName)) {
this.nodeDatabase[selectedNode.index].data[LNDCollection[collectionName]] = this.nodeDatabase[selectedNode.index].adapter.fetchData(LNDCollection[collectionName]);
}
}
break;
}
}
validateDocument(collectionName, newDocument) {
return new Promise((resolve, reject) => {
const validationRes = validateDocument(collectionName, newDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
} else {
resolve(true);
}
});
}
insert(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, newCollection: any) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
reject(new Error('Selected Node Config Not Found.'));
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, newDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
} else {
this.nodeDatabase[selectedNode.index].data[collectionName].push(newDocument);
this.saveDatabase(+selectedNode.index);
resolve(newDocument);
}
this.nodeDatabase[selectedNode.index].data[collectionName] = newCollection;
this.saveDatabase(selectedNode, collectionName);
resolve(this.nodeDatabase[selectedNode.index].data[collectionName]);
} catch (errRes) {
reject(errRes);
}
@ -67,10 +105,6 @@ export class DatabaseService {
}
updatedDocument = foundDoc;
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, updatedDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
} else {
if (foundDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(foundDocIdx, 1, updatedDocument);
} else {
@ -79,9 +113,8 @@ export class DatabaseService {
}
this.nodeDatabase[selectedNode.index].data[collectionName].push(updatedDocument);
}
this.saveDatabase(+selectedNode.index);
this.saveDatabase(selectedNode, collectionName);
resolve(updatedDocument);
}
} catch (errRes) {
reject(errRes);
}
@ -105,7 +138,7 @@ export class DatabaseService {
});
}
destroy(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, documentFieldName: string, documentFieldValue: string) {
remove(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum, documentFieldName: string, documentFieldValue: string) {
return new Promise((resolve, reject) => {
try {
if (!selectedNode || !selectedNode.index) {
@ -117,7 +150,7 @@ export class DatabaseService {
} else {
reject(new Error('Unable to delete, document not found.'));
}
this.saveDatabase(+selectedNode.index);
this.saveDatabase(selectedNode, collectionName);
resolve(documentFieldValue);
} catch (errRes) {
reject(errRes);
@ -125,19 +158,10 @@ export class DatabaseService {
});
}
validateDocument(collectionName: CollectionsEnum, documentToValidate: any) {
switch (collectionName) {
case CollectionsEnum.OFFERS:
return validateOffer(documentToValidate);
default:
return ({ isValid: false, error: 'Collection does not exist' });
}
}
saveDatabase(nodeIndex: number) {
saveDatabase(selectedNode: CommonSelectedNode, collectionName: CollectionsEnum) {
const nodeIndex = +selectedNode.index;
try {
if (+nodeIndex < 1) {
if (nodeIndex < 1) {
return true;
}
const selNode = this.nodeDatabase[nodeIndex] && this.nodeDatabase[nodeIndex].adapter && this.nodeDatabase[nodeIndex].adapter.selNode ? this.nodeDatabase[nodeIndex].adapter.selNode : null;
@ -145,51 +169,103 @@ export class DatabaseService {
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error: Selected Node Setup Not Found.' });
throw new Error('Database Save Error: Selected Node Setup Not Found.');
}
this.nodeDatabase[nodeIndex].adapter.saveData(this.nodeDatabase[nodeIndex].data);
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'INFO', fileName: 'Database', msg: 'Database Saved' });
this.nodeDatabase[nodeIndex].adapter.saveData(collectionName, this.nodeDatabase[selectedNode.index].data[collectionName]);
this.logger.log({ selectedNode: this.nodeDatabase[nodeIndex].adapter.selNode, level: 'INFO', fileName: 'Database', msg: 'Database Collection ' + collectionName + ' Saved' });
return true;
} catch (err) {
const selNode = this.nodeDatabase[nodeIndex] && this.nodeDatabase[nodeIndex].adapter && this.nodeDatabase[nodeIndex].adapter.selNode ? this.nodeDatabase[nodeIndex].adapter.selNode : null;
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Database Save Error', error: err });
return new Error(err);
throw err;
}
}
unloadDatabase(nodeIndex: number) {
this.saveDatabase(nodeIndex);
this.nodeDatabase[nodeIndex] = null;
unloadDatabase(nodeIndex: number, sessionID: string) {
if (nodeIndex > 0) {
if (this.nodeDatabase[nodeIndex] && this.nodeDatabase[nodeIndex].adapter) {
this.nodeDatabase[nodeIndex].adapter.removeSession(sessionID);
if (this.nodeDatabase[nodeIndex].adapter.userSessions && this.nodeDatabase[nodeIndex].adapter.userSessions.length <= 0) {
delete this.nodeDatabase[nodeIndex];
}
}
}
}
}
export class DatabaseAdapter {
private dbFile = '';
private logger: LoggerService = Logger;
private common: CommonService = Common;
private dbFilePath = '';
private userSessions = [];
constructor(public dbDirectoryPath: string, public fileName: string, private selNode: CommonSelectedNode = null) {
this.dbFile = dbDirectoryPath + sep + fileName + '-node-' + selNode.index + '.json';
constructor(public dbDirectoryPath: string, private selNode: CommonSelectedNode = null, private id: string = '') {
this.dbFilePath = dbDirectoryPath + sep + 'node-' + selNode.index;
// For backward compatibility Start
const oldFilePath = dbDirectoryPath + sep + 'rtldb-node-' + selNode.index + '.json';
if (selNode.ln_implementation === 'CLN' && fs.existsSync(oldFilePath)) { this.renameOldDB(oldFilePath, selNode); }
// For backward compatibility End
this.insertSession(id);
}
fetchData() {
renameOldDB(oldFilePath: string, selNode: CommonSelectedNode = null) {
const newFilePath = this.dbFilePath + sep + 'rtldb-' + selNode.ln_implementation + '-Offers.json';
try {
if (!fs.existsSync(this.dbDirectoryPath)) {
fs.mkdirSync(this.dbDirectoryPath);
this.common.createDirectory(this.dbFilePath);
const oldOffers: any = JSON.parse(fs.readFileSync(oldFilePath, 'utf-8'));
fs.writeFileSync(oldFilePath, JSON.stringify(oldOffers.Offers, null, 2));
fs.renameSync(oldFilePath, newFilePath);
} catch (err) {
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Rename Old Database Error', error: err });
}
}
fetchData(collectionName: string) {
try {
if (!fs.existsSync(this.dbFilePath)) {
this.common.createDirectory(this.dbFilePath);
}
} catch (err) {
return new Error('Unable to Create Directory Error ' + JSON.stringify(err));
throw new Error(JSON.stringify(err));
}
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.ln_implementation + '-' + collectionName + '.json';
try {
if (!fs.existsSync(this.dbFile)) {
fs.writeFileSync(this.dbFile, '{}');
if (!fs.existsSync(collectionFilePath)) {
fs.writeFileSync(collectionFilePath, '[]');
}
} catch (err) {
return new Error('Unable to Create Database File Error ' + JSON.stringify(err));
throw new Error(JSON.stringify(err));
}
try {
const dataFromFile = fs.readFileSync(this.dbFile, 'utf-8');
return !dataFromFile ? null : (<Collections>JSON.parse(dataFromFile));
const otherFiles = fs.readdirSync(this.dbFilePath);
otherFiles.forEach((oFileName) => {
let collectionValid = false;
switch (this.selNode.ln_implementation) {
case 'CLN':
collectionValid = CLNCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
break;
case 'ECL':
collectionValid = ECLCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
break;
default:
collectionValid = LNDCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
break;
}
if (oFileName.endsWith('.json') && !collectionValid) {
fs.renameSync(this.dbFilePath + sep + oFileName, this.dbFilePath + sep + oFileName + '.tmp');
}
});
} catch (err) {
return new Error('Database Read Error ' + JSON.stringify(err));
this.logger.log({ selectedNode: this.selNode, level: 'ERROR', fileName: 'Database', msg: 'Rename Other Implementation DB Error', error: err });
}
try {
const dataFromFile = fs.readFileSync(collectionFilePath, 'utf-8');
const dataObj = !dataFromFile ? null : (<Collections>JSON.parse(dataFromFile));
return dataObj;
} catch (err) {
throw new Error(JSON.stringify(err));
}
}
@ -197,19 +273,30 @@ export class DatabaseAdapter {
return this.selNode;
}
saveData(data: any) {
saveData(collectionName: string, collectionData: any) {
try {
if (data) {
const tempFile = this.dbFile + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(data, null, 2));
fs.renameSync(tempFile, this.dbFile);
if (collectionData) {
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.ln_implementation + '-' + collectionName + '.json';
const tempFile = collectionFilePath + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(collectionData, null, 2));
fs.renameSync(tempFile, collectionFilePath);
}
return true;
} catch (err) {
return new Error('Database Write Error ' + JSON.stringify(err));
throw err;
}
}
insertSession(id: string = '') {
if (!this.userSessions.includes(id)) {
this.userSessions.push(id);
}
}
removeSession(sessionID: string = '') {
this.userSessions.splice(this.userSessions.findIndex((sId) => sId === sessionID), 1);
}
}
export const Database = new DatabaseService();

View File

@ -9,8 +9,10 @@ export class LoggerService {
switch (msgJSON.level) {
case 'ERROR':
if (msgJSON.error) {
msgStr = msgStr + ': ' + ((msgJSON.error.error && msgJSON.error.error.message && typeof msgJSON.error.error.message === 'string') ? msgJSON.error.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.message && typeof msgJSON.error.message === 'string') ? msgJSON.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.stack && typeof msgJSON.error.stack === 'string') ?
msgJSON.error.stack : (typeof msgJSON.error === 'object') ? JSON.stringify(msgJSON.error) : (typeof msgJSON.error === 'string') ? msgJSON.error : '') + '\r\n';
msgStr = msgStr + ': ' + ((msgJSON.error.error && msgJSON.error.error.message && typeof msgJSON.error.error.message === 'string') ?
msgJSON.error.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.message && typeof msgJSON.error.message === 'string') ? msgJSON.error.message : (typeof msgJSON.error === 'object' && msgJSON.error.stack && typeof msgJSON.error.stack === 'string') ?
msgJSON.error.stack : (typeof msgJSON.error === 'object') ? JSON.stringify(msgJSON.error) : (typeof msgJSON.error === 'string') ?
msgJSON.error : '') + '\r\n';
} else {
msgStr = msgStr + '.\r\n';
}
@ -69,7 +71,9 @@ export class LoggerService {
const prepMsgData = (msgJSON, msgStr) => {
if (msgJSON.data) {
msgStr = msgStr + ': ' + (typeof msgJSON.data === 'object' ? (msgJSON.data.message && typeof msgJSON.data.message === 'string') ? msgJSON.data.message : (msgJSON.data.stack && typeof msgJSON.data.stack === 'string') ? msgJSON.data.stack : JSON.stringify(msgJSON.data) : (typeof msgJSON.data === 'string') ? msgJSON.data : '') + '\r\n';
msgStr = msgStr + ': ' + (typeof msgJSON.data === 'object' ? (msgJSON.data.message && typeof msgJSON.data.message === 'string') ?
msgJSON.data.message : (msgJSON.data.stack && typeof msgJSON.data.stack === 'string') ?
msgJSON.data.stack : JSON.stringify(msgJSON.data) : (typeof msgJSON.data === 'string') ? msgJSON.data : '') + '\r\n';
} else {
msgStr = msgStr + '.\r\n';
}

View File

@ -57,10 +57,10 @@ export class RTLWebSocketServer {
public mountEventsOnConnection = (websocket, request) => {
const protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
const cookies = parse(request.headers.cookie);
const cookies = request.headers.cookie ? parse(request.headers.cookie) : null;
websocket.clientId = Date.now();
websocket.isAlive = true;
websocket.sessionId = cookieParser.signedCookie(cookies['connect.sid'], this.common.secret_key);
websocket.sessionId = cookies && cookies['connect.sid'] ? cookieParser.signedCookie(cookies['connect.sid'], this.common.secret_key) : null;
websocket.clientNodeIndex = +protocols[1];
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
websocket.on('error', this.sendErrorToAllLNClients);

View File

@ -39,7 +39,7 @@ import { ECLReducer } from './eclair/store/ecl.reducers';
routing,
LayoutModule,
HammerModule,
UserIdleModule.forRoot({ idle: 3590, timeout: 10, ping: 12000 }), // One hour
UserIdleModule.forRoot({ idle: 3590, timeout: 10, ping: 12000 }), // One hour => 3590 + 10 = 3600
StoreModule.forRoot(
{ root: RootReducer, lnd: LNDReducer, cln: CLNReducer, ecl: ECLReducer },
{

View File

@ -8,6 +8,7 @@ import { BitcoinConfigComponent } from './shared/components/settings/bitcoin-con
import { NodeConfigComponent } from './shared/components/node-config/node-config.component';
import { LNPConfigComponent } from './shared/components/node-config/lnp-config/lnp-config.component';
import { NodeSettingsComponent } from './shared/components/node-config/node-settings/node-settings.component';
import { PageSettingsComponent } from './shared/components/node-config/page-settings/page-settings.component';
import { ServicesSettingsComponent } from './shared/components/node-config/services-settings/services-settings.component';
import { LoopServiceSettingsComponent } from './shared/components/node-config/services-settings/loop-service-settings/loop-service-settings.component';
import { BoltzServiceSettingsComponent } from './shared/components/node-config/services-settings/boltz-service-settings/boltz-service-settings.component';
@ -36,8 +37,9 @@ export const routes: Routes = [
},
{
path: 'config', component: NodeConfigComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'layout' },
{ path: 'layout', component: NodeSettingsComponent, canActivate: [AuthGuard] },
{ path: '', pathMatch: 'full', redirectTo: 'nodesettings' },
{ path: 'nodesettings', component: NodeSettingsComponent, canActivate: [AuthGuard] },
{ path: 'pglayout', component: PageSettingsComponent, canActivate: [AuthGuard] },
{
path: 'services', component: ServicesSettingsComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'loop' },
@ -65,4 +67,4 @@ export const routes: Routes = [
];
// Export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { enableTracing: true });
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes);
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' });

View File

@ -8,7 +8,7 @@
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="20" class="my-1">
<h4 class="font-bold-500">Short Channel Id</h4>
<h4 class="font-bold-500">Short Channel ID</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.short_channel_id}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
@ -89,7 +89,7 @@
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="20" class="my-1">
<h4 class="font-bold-500">Short Channel Id</h4>
<h4 class="font-bold-500">Short Channel ID</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.short_channel_id}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>

Some files were not shown because too many files have changed in this diff Show More