Merged 0.14.1

cln-peer-swap
ShahanaFarooqui 10 months ago
parent 781974b8e7
commit ceb8b00e1f

@ -1,17 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

@ -1,10 +1,7 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*",
"rtl.js",
"/backend/**/*.js",
"/src/prebuild.js"
"src/**/*.js"
],
"overrides": [
{
@ -13,11 +10,12 @@
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"ecmaVersion": 2022,
"sourceType": "module",
"project": "./tsconfig.json",
"createDefaultProgram": true
},
"env": { "es2022": true },
"plugins": ["deprecation"],
"extends": [
"plugin:@angular-eslint/all",
@ -48,7 +46,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"],
@ -186,22 +184,30 @@
"files": [
"*.html"
],
"parser": "@angular-eslint/template-parser",
"extends": [
"plugin:@angular-eslint/template/all",
"plugin:@angular-eslint/template/recommended"
"plugin:@angular-eslint/template/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@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" }],
"@angular-eslint/template/accessibility-elements-content": "off",
"@angular-eslint/template/accessibility-interactive-supports-focus": "off",
"@angular-eslint/template/button-has-type": "off",
"@angular-eslint/template/click-events-have-key-events": "off",
"@angular-eslint/template/conditional-complexity": "off",
"@angular-eslint/template/cyclomatic-complexity": "off",
"@angular-eslint/template/i18n": "off",
"@angular-eslint/template/no-autofocus": "off",
"@angular-eslint/template/no-call-expression": "off",
"@angular-eslint/template/no-inline-styles": "off",
"@angular-eslint/template/no-interpolation-in-attributes": "off",
"@angular-eslint/template/no-positive-tabindex": "off",
"@angular-eslint/template/use-track-by-function": "off"
}
}
]
}
// https://eslint.org/docs/rules/

11
.github/README.md vendored

@ -35,6 +35,7 @@ RTL is available on the below platforms/services:
* [BCubium](https://bgeometrics.com)
* [Start9Labs](https://start9labs.com)
* [Umbrel](https://github.com/getumbrel/umbrel)
* [Sovran Systems](https://sovransystems.com)
Docker Image: https://hub.docker.com/r/shahanafarooqui/rtl
@ -64,6 +65,10 @@ $ git clean -f -d
$ git pull
$ npm install --omit=dev
```
#### Error on npm install
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
@ -79,6 +84,7 @@ Example RTL-Config.json:
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -104,9 +110,10 @@ 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>"
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. https://127.0.0.1:8081>",
"boltzServerUrl": "<url for boltz server REST APIs for the node. e.g. https://127.0.0.1:9003>"
}
}
]

@ -9,16 +9,17 @@ parameters have `default` values for initial setup and can be updated after RTL
"port": "<port number for the rtl node server, default '3000', Required>",
"host": "<host for the rtl node server, default 'all IPs', Optional>",
"defaultNodeIndex": <Default index to load when rtl server starts, default 1, Optional>,
"dbDirectoryPath": "<Complete path of the folder where rtl database file should be saved, defults to RTL root, Optional>",
"SSO": {
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), default 0, Required>,
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), Required>,
"rtlCookiePath": "<Full path of the cookie file including the file name. The application url needs to pass the value from this cookie file as query param 'access-key' for the SSO authentication to work, Required if SSO=1 else empty (Optional)>",
"logoutRedirectLink": "<URL to re-direct to after logout/timeout from RTL, Required if SSO=1 else empty (Optional)>"
},
"nodes": [
{
"index": <Incremental node indices starting from 1, Required>,
"lnNode": "<Node name to uniquely identify the node in the UI, Default 'Node 1', Required>",
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL. Default 'LND', Required>",
"lnNode": "<Node name to uniquely identify the node in the UI, Required>",
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL, Required>",
"Authentication": {
"macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN>",
"swapMacaroonPath": "<Path for the folder containing 'loop.macaroon' (LND), Required for LND Loop>",
@ -27,17 +28,18 @@ parameters have `default` values for initial setup and can be updated after RTL
"lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>"
},
"Settings": {
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT, OPERATOR. Default MERCHANT, Required>",
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Required>",
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK, YELLOW. Default PURPLE, Required>",
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT/OPERATOR. Default MERCHANT, Optional>",
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Optional>",
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK, YELLOW. Default PURPLE, Optional>",
"channelBackupPath": "<Path to save channel backup file. Only for LND implementation, Default <RTL root>\backup\node-1, Optional>",
"bitcoindConfigPath": "<Path of bitcoind.conf path if available locally>",
"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>",
"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>"
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Optional>,
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD', Optional>",
"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://127.0.0.1:8080', Optional>
"swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. https://127.0.0.1:8081, Optional>",
"boltzServerUrl": "<Service url for boltz server REST APIs for the node, e.g. https://127.0.0.1:9003, Optional>"
}
}
]
@ -48,13 +50,14 @@ parameters have `default` values for initial setup and can be updated after RTL
The environment variable can also be used for all of the above configurations except the UI settings.<br />
If the environment variables are set, it will take precedence over the parameters in the RTL-Config.json file.<br />
<br />
PORT (port number for the rtl node server, default 3000, Required)<br />
PORT (port number for the rtl node server, default 3000, Optional)<br />
HOST (host for the rtl node server, default localhost, Optional)<br />
DB_DIRECTORY_PATH (Path for the folder where rtl database file should be saved, default RTL root directory, Optional)
APP_PASSWORD (Plaintext password to be provided by the parent container, NOT suggested for standalone RTL applications, to be used by Umbrel) (Optional)<br />
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Required)<br />
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://localhost:8080) (Required)<br />
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://localhost:8081) (Optional)<br />
BOLTZ_SERVER_URL (Boltz server URL for REST APIs, default http://localhost:9003) (Optional)<br />
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Optional)<br />
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080) (Optional)<br />
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://127.0.0.1:8081) (Optional)<br />
BOLTZ_SERVER_URL (Boltz server URL for REST APIs, default http://127.0.0.1:9003) (Optional)<br />
CONFIG_PATH (Full path of the LNP .conf file including the file name) (Optional for LND & CLN, Mandatory for ECL if LN_API_PASSWORD is undefined)<br />
MACAROON_PATH (Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN)<br />
SWAP_MACAROON_PATH (Path for the folder containing Loop's 'loop.macaroon', optional)<br />

@ -44,6 +44,7 @@ Contributions via code is the most sought after contribution and something we en
##### Install Dependencies
* Assuming that nodejs (v14 & above) and npm are already installed on your local machine. Go into your RTL root folder and run `npm install`.
* Use `npm install --legacy-peer-deps` if there is any dependency conflict.
* Sometimes after installation, user receives a message from npm to fix dependency vulnerability by running `npm audit fix`. Please do not follow this step as it can break some of the working RTL code on your machine. We audit and fix these vulnerabilities as soon as possible at our end.
##### Node Backend Server for Development

@ -43,6 +43,10 @@ $ git clean -f -d
$ git pull
$ npm install --omit=dev
```
#### Error on npm install
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`..
@ -61,6 +65,7 @@ Ensure that the follow values are correct per your config:
"multiPass": <password required for accessing RTL>,
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -82,6 +87,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"
}
}

@ -38,6 +38,10 @@ $ git clean -f -d
$ git pull
$ npm install --omit=dev
```
#### Error on npm install
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`..
@ -56,6 +60,7 @@ Ensure that the follow values are correct per your config:
"multiPass": <password required for accessing RTL>,
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -77,6 +82,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"
}
}

@ -20,16 +20,17 @@ This step is only required to configure the nodes, which will be remotely connec
2. Set `multiPass` to the preferred password. This password will be used to authenticate the user for RTL. Once authenticated, the user will be able to access all the nodes configured in the json file
3. Set the `port` to the preferred port number over which to run RTL
4. Set the `defaultNodeIndex` to configure the default start up node at server restart
5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
7. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
8. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop.
9. `boltzMacaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for boltz swaps.
10. `lnServerUrl` must be set to the service url for LND/Core Lightining REST APIs for each node, with the unique ip address of the node hosting LND/Core Lightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting LND/Core Lightning is '192.168.0.1'
11. `swapServerUrl` must be set to the swap service url. e.g. https://localhost:8081.
12. `boltzServerUrl` must be set to the boltz service url. e.g. https://localhost:9003.
13. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
14. `lnApiPassword` is mandatory in the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations.
5. `dbDirectoryPath` should be set to the folder where RTL's database will be saved.
6. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
7. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
8. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
9. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop.
10. `boltzMacaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for boltz swaps.
11. `lnServerUrl` must be set to the service url for LND/Core Lightining REST APIs for each node, with the unique ip address of the node hosting LND/Core Lightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting LND/Core Lightning is '192.168.0.1'
12. `swapServerUrl` must be set to the swap service url. e.g. https://127.0.0.1:8081.
13. `boltzServerUrl` must be set to the boltz service url. e.g. https://127.0.0.1:9003.
14. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
15. `lnApiPassword` is mandatory in the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations.
#### 3. Restart RTL

@ -3,20 +3,21 @@
Forward the ports 80 and 3002 on the router to the device running RTL.
Allow the ports through the firewall of the device.
Install Nginx:
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/
On Debian based distros:
$> sudo apt install nginx
nginx default config file is at /etc/nginx/nginx.conf. You will need it.
Install, if needed, openssl
On Debian based distros:
$> sudo apt install openssl
Create a self certificate with openssl
$> openssl req -newkey rsa:4096 -x509 -sha512 -days 365 -nodes -out /path/to/some/folder/rtl-cert.crt -keyout /path/to/some/folder/rtl-cert.key
#### Nginx
Install Nginx:
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/
On Debian based distros:
$> sudo apt install nginx
nginx default config file is at /etc/nginx/nginx.conf. You will need it.
Sample configuration to be inserted in the nginx.conf (adjust the path and filename of your certificate and key):
@ -41,3 +42,41 @@ Sample configuration to be inserted in the nginx.conf (adjust the path and filen
Restart Nginx with the new configuration and connect to RTL over https on the port 3002.
On Debian based distros:
$> sudo systemctl restart nginx
#### Apache2
Skip to step 5 if you already have Apache2 set up with HTTPS configured.
1. Install [Apache2](https://httpd.apache.org/download.cgi)
- On Debian-based distros: `sudo apt install apache2`
- On Fedora-based distros: `sudo yum install httpd`
2. Install [Let's Encrypt](https://letsencrypt.org) to get a free TLS certificate. The easiest way to install is to use Snap: `sudo snap install certbot`.
3. Run `certbot` to get a Let's Encrypt certificate: `sudo certbot`. Follow the instructions given to validate your domain name and install the certificate only. You may choose to redirect HTTP traffic to HTTPS instead (which attempts to secure every connection even when the client device does not request it). Let's Encrypt does not issue certificates for IP addresses. if you don't have a domain name, but you can use a service like NoIP.
4. Locate the Let's Encrypt Apache2 configuration file. It's usually in `/etc/apache2/sites-enabled` and called "000-default-le-ssl.conf" or similar.
5. Add the following lines to the Apache2 configuration file between the VIrtualHost 443 tags. This will redirect Apache's document root on your webserver to instead point to RTL. Change "/" to something like "/rtl" if you would instead like to redirect "/rtl" to RTL and do something else at the document root. Change "3002" to whatever port number you are using if it is not 3002.
ProxyPass "/" "http://127.0.0.1:3002/rtl"
ProxyPassReverse "/" "http://127.0.0.1:3002/rtl"
6. Restart Apache2.
- Debian-based distros: `sudo systemctl restart apache2`
- Fedora-based distros: `sudo systemctl restart httpd`
7. (Option) Edit ~/.rtl/rtl.js to disable insercure HTTP access to RTL entirely. Find these lines:
if (common.host) {
server.listen(common.port, common.host);
} else {
server.listen(common.port);
}
...and change them to:
if (common.host) {
server.listen(common.port, "127.0.0.1");
} else {
server.listen(common.port, "127.0.0.1");
}
This disables normal HTTP access to your server except if the client is on the same machine as the server. This will allow you to access http://localhost:3002 (or whatever port number you are using) on a browser that is on the same machine as RTL, but otherwise, you will have to access RTL through the Apache2 reverse proxy at https://yourdomain.tld/rtl, which will secure the connection with HTTPS.
Note: Occasionally you will receive "Invalid CSRF token, form tempered" when attempting to log in to RTL. If that happens, refresh the page and try again.

@ -15,6 +15,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"multiPass": "<password in plain text, Default 'password'>",
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -39,6 +40,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>"

@ -33,7 +33,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
lint:
name: Lint
@ -57,7 +57,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
- name: Lint Scripts
run: npm run lint
@ -89,7 +89,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
- name: Run tests
run: npm run test

@ -34,7 +34,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
- name: Cache build frontend
uses: actions/cache@v2

@ -2,6 +2,7 @@
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "C:\\Users\\xyz\\RTL",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -24,10 +25,11 @@
"themeColor": "PURPLE",
"channelBackupPath": "C:\\Users\\xyz\\backup\\node-1",
"logLevel": "ERROR",
"lnServerUrl": "https://localhost:8080",
"swapServerUrl": "https://localhost:8081",
"boltzServerUrl": "https://localhost:9003",
"fiatConversion": false
"lnServerUrl": "https://127.0.0.1:8080",
"swapServerUrl": "https://127.0.0.1:8081",
"boltzServerUrl": "https://127.0.0.1:9003",
"fiatConversion": false,
"unannouncedChannels": false
}
}
]

@ -8,9 +8,6 @@
"schematics": {
"@schematics/angular:component": {
"style": "scss"
},
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
@ -24,70 +21,69 @@
"outputPath": "frontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"allowedCommonJsDependencies": [
"sha256",
"qrcode",
"otplib",
"pdfmake/build/pdfmake",
"pdfmake/build/vfs_fonts",
"clone-deep"
"polyfills": [
"zone.js",
"src/polyfills.ts"
],
"tsConfig": "src/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
],
"styles": [
"src/app/shared/theme/styles/styles.scss"
"src/app/shared/theme/styles/styles.scss",
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
"allowedCommonJsDependencies": [
"buffer",
"rfdc",
"sha256",
"qrcode",
"otplib",
"pdfmake/build/pdfmake",
"pdfmake/build/vfs_fonts"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "12mb",
"maximumError": "12mb"
"maximumWarning": "20mb",
"maximumError": "50mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20mb",
"maximumError": "50mb"
}
]
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "RTLApp:build"
},
"configurations": {
"production": {
"browserTarget": "RTLApp:build:production"
},
"development": {
"browserTarget": "RTLApp:build:development"
}
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
@ -98,15 +94,20 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"zone.js",
"zone.js/testing",
"src/polyfills.ts"
],
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.cjs",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
],
"styles": [
"src/app/shared/theme/styles/styles.scss"
"src/app/shared/theme/styles/styles.scss",
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
],
"scripts": []
}
@ -119,12 +120,11 @@
"src/**/*.html"
]
}
}
}
}
}
},
"defaultProject": "RTLApp",
"cli": {
"defaultCollection": "@angular-eslint/schematics"
"analytics": false
}
}

@ -4,21 +4,48 @@ import { Common } from '../../utils/common.js';
let options = null;
const logger = Logger;
const common = Common;
export const listPeerChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' });
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/channel/listPeerChannels';
request(options).then((body) => {
body?.map((channel) => {
if (!channel.alias || channel.alias === '') {
channel.alias = channel.peer_id.substring(0, 20);
}
const local = channel.to_us_msat || 0;
const remote = (channel.total_msat - local) || 0;
const total = channel.total_msat || 0;
channel.to_them_msat = remote;
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
});
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'List Peer Channels Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const listChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
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/channel/listChannels';
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
request(options).then((body) => {
body === null || body === void 0 ? void 0 : body.map((channel) => {
body?.map((channel) => {
if (!channel.alias || channel.alias === '') {
channel.alias = channel.id.substring(0, 20);
channel.alias = channel.channel_id.substring(0, 20);
}
const local = (channel.msatoshi_to_us) ? channel.msatoshi_to_us : 0;
const remote = (channel.msatoshi_to_them) ? channel.msatoshi_to_them : 0;
const total = channel.msatoshi_total ? channel.msatoshi_total : 0;
const local = channel.to_us_msat || 0;
const remote = (channel.total_msat - local) || 0;
const total = channel.total_msat || 0;
channel.to_them_msat = remote;
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
});
@ -129,10 +156,9 @@ export const funderUpdatePolicy = (req, res, next) => {
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
request.post(options).then((body) => {
var _a, _b;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body });
body.channel_fee_max_base_msat = (body.channel_fee_max_base_msat && typeof body.channel_fee_max_base_msat === 'string' && body.channel_fee_max_base_msat.includes('msat')) ? +((_a = body.channel_fee_max_base_msat) === null || _a === void 0 ? void 0 : _a.replace('msat', '')) : body.channel_fee_max_base_msat;
body.lease_fee_base_msat = (body.lease_fee_base_msat && typeof body.lease_fee_base_msat === 'string' && body.lease_fee_base_msat.includes('msat')) ? +((_b = body.lease_fee_base_msat) === null || _b === void 0 ? void 0 : _b.replace('msat', '')) : body.channel_fee_max_base_msat;
body.channel_fee_max_base_msat = (body.channel_fee_max_base_msat && typeof body.channel_fee_max_base_msat === 'string' && body.channel_fee_max_base_msat.includes('msat')) ? +body.channel_fee_max_base_msat?.replace('msat', '') : body.channel_fee_max_base_msat;
body.lease_fee_base_msat = (body.lease_fee_base_msat && typeof body.lease_fee_base_msat === 'string' && body.lease_fee_base_msat.includes('msat')) ? +body.lease_fee_base_msat?.replace('msat', '') : body.channel_fee_max_base_msat;
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);

@ -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);
}

@ -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);

@ -76,10 +76,11 @@ export const listNodes = (req, res, next) => {
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
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')) ? 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;
});

@ -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) => {

@ -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) => {

@ -22,8 +22,8 @@ function paymentReducer(accumulator, currentPayment) {
}
function summaryReducer(accumulator, mpp) {
if (mpp.status === 'complete') {
accumulator.msatoshi = accumulator.msatoshi + mpp.msatoshi;
accumulator.msatoshi_sent = accumulator.msatoshi_sent + mpp.msatoshi_sent;
accumulator.amount_msat = accumulator.amount_msat + mpp.amount_msat;
accumulator.amount_sent_msat = accumulator.amount_sent_msat + mpp.amount_sent_msat;
accumulator.status = mpp.status;
}
if (mpp.bolt11) {
@ -35,10 +35,9 @@ function summaryReducer(accumulator, mpp) {
return accumulator;
}
function groupBy(payments) {
var _a;
const paymentsInGroups = payments === null || payments === void 0 ? void 0 : payments.reduce(paymentReducer, {});
const paymentsGrpArray = (_a = Object.keys(paymentsInGroups)) === null || _a === void 0 ? void 0 : _a.map((key) => ((paymentsInGroups[key].length && paymentsInGroups[key].length > 1) ? common.sortDescByKey(paymentsInGroups[key], 'partid') : paymentsInGroups[key]));
return paymentsGrpArray === null || paymentsGrpArray === void 0 ? void 0 : paymentsGrpArray.reduce((acc, curr) => {
const paymentsInGroups = payments?.reduce(paymentReducer, {});
const paymentsGrpArray = Object.keys(paymentsInGroups)?.map((key) => ((paymentsInGroups[key].length && paymentsInGroups[key].length > 1) ? common.sortDescByKey(paymentsInGroups[key], 'partid') : paymentsInGroups[key]));
return paymentsGrpArray?.reduce((acc, curr) => {
let temp = {};
if (curr.length && curr.length === 1) {
temp = JSON.parse(JSON.stringify(curr[0]));
@ -48,10 +47,10 @@ function groupBy(payments) {
delete temp.partid;
}
else {
const paySummary = curr === null || curr === void 0 ? void 0 : curr.reduce(summaryReducer, { msatoshi: 0, msatoshi_sent: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
const paySummary = curr?.reduce(summaryReducer, { amount_msat: 0, amount_sent_msat: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
temp = {
is_group: true, is_expanded: false, total_parts: (curr.length ? curr.length : 0), status: paySummary.status, payment_hash: curr[0].payment_hash,
destination: curr[0].destination, msatoshi: paySummary.msatoshi, msatoshi_sent: paySummary.msatoshi_sent, created_at: curr[0].created_at,
destination: curr[0].destination, amount_msat: paySummary.amount_msat, amount_sent_msat: paySummary.amount_sent_msat, created_at: curr[0].created_at,
mpps: curr
};
if (paySummary.bolt11) {
@ -73,10 +72,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,19 +103,25 @@ 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() };
if (req.body.vendor) {
offerToUpdate['vendor'] = req.body.vendor;
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.issuer) {
offerToUpdate['issuer'] = req.body.issuer;
}
if (req.body.description) {
offerToUpdate['description'] = req.body.description;
}
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 });
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 });
// 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: '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 {

@ -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) => {

@ -19,7 +19,7 @@ export const decodePayments = (req, res, next) => {
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr === null || paymentsArr === void 0 ? void 0 : paymentsArr.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
return Promise.all(paymentsArr?.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment List Decoded', data: values });
res.status(200).json(values);

@ -47,9 +47,8 @@ export class CLWebSocketClient {
}
};
this.connectWithClient = (clWsClt) => {
var _a;
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' });
const WS_LINK = ((_a = (clWsClt.selectedNode.ln_server_url)) === null || _a === void 0 ? void 0 : _a.replace(/^http/, 'ws')) + '/v1/ws';
const WS_LINK = (clWsClt.selectedNode.ln_server_url)?.replace(/^http/, 'ws') + '/v1/ws';
const mcrnHexEncoded = Buffer.from(fs.readFileSync(join(clWsClt.selectedNode.macaroon_path, 'access.macaroon'))).toString('hex');
clWsClt.webSocketClient = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false });
clWsClt.webSocketClient.onopen = () => {
@ -73,17 +72,12 @@ export class CLWebSocketClient {
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
};
clWsClt.webSocketClient.onerror = (err) => {
if (clWsClt.selectedNode.api_version === '' || !clWsClt.selectedNode.api_version || this.common.isVersionCompatible(clWsClt.selectedNode.api_version, '0.6.0')) {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', error: err });
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
clWsClt.webSocketClient.close();
if (clWsClt.reConnect) {
this.reconnet(clWsClt);
}
}
else {
clWsClt.reConnect = false;
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', error: err });
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
clWsClt.webSocketClient.close();
if (clWsClt.reConnect) {
this.reconnet(clWsClt);
}
};
};

@ -1,6 +1,9 @@
import request from 'request-promise';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { createInvoiceRequestCall, listPendingInvoicesRequestCall } from './invoices.js';
import { findRouteBetweenNodesRequestCall } from './network.js';
import { getSentInfoFromPaymentRequest, sendPaymentToRouteRequestCall } from './payments.js';
let options = null;
const logger = Logger;
const common = Common;
@ -13,10 +16,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,
@ -32,7 +35,7 @@ export const simplifyAllChannels = (selNode, channels) => {
return request.post(options).then((nodes) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
let foundPeer = null;
simplifiedChannels === null || simplifiedChannels === void 0 ? void 0 : simplifiedChannels.map((channel) => {
simplifiedChannels?.map((channel) => {
foundPeer = nodes.find((channelWithAlias) => channel.nodeId === channelWithAlias.nodeId);
channel.alias = foundPeer ? foundPeer.alias : channel.nodeId.substring(0, 20);
return channel;
@ -83,7 +86,13 @@ export const getChannelStats = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/channelstats';
options.form = {};
const today = new Date(Date.now());
const tillToday = (Math.round(today.getTime() / 1000)).toString();
const fromLastMonth = (Math.round(new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime() / 1000)).toString();
options.form = {
from: fromLastMonth,
to: tillToday
};
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel States Received', data: body });
res.status(201).json(body);
@ -150,3 +159,54 @@ export const closeChannel = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
// options.form = { sourceNodeId: req.params.source, targetNodeId: req.params.target, amountMsat: req.params.amount, ignoreNodeIds: req.params.ignore };
export const circularRebalance = (req, res, next) => {
const crInvDescription = 'Circular rebalancing invoice for ' + (req.body.amountMsat / 1000) + ' Sats';
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.form = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Rebalance Params', data: options.form });
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
// Check if unpaid Invoice exists already
listPendingInvoicesRequestCall(req.session.selectedNode).then((callRes) => {
const foundExistingInvoice = callRes.find((inv) => inv.description.includes(crInvDescription) && inv.amount === req.body.amountMsat && inv.expiry && inv.timestamp && ((inv.expiry + inv.timestamp) >= tillToday));
// Create new invoice if doesn't exist already
const requestCalls = foundExistingInvoice && foundExistingInvoice.serialized ?
[findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format)] :
[findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format), createInvoiceRequestCall(req.session.selectedNode, crInvDescription, req.body.amountMsat)];
Promise.all(requestCalls).then((values) => {
// eslint-disable-next-line arrow-body-style
const routes = values[0]?.routes?.filter((route) => {
return !((route.shortChannelIds[0] === req.body.sourceShortChannelId && route.shortChannelIds[1] === req.body.targetShortChannelId) ||
(route.shortChannelIds[1] === req.body.sourceShortChannelId && route.shortChannelIds[0] === req.body.targetShortChannelId));
});
const firstRoute = routes[0].shortChannelIds.join() || '';
const shortChannelIds = req.body.sourceShortChannelId + ',' + firstRoute + ',' + req.body.targetShortChannelId;
const invoice = (foundExistingInvoice && foundExistingInvoice.serialized ? foundExistingInvoice.serialized : (values[1] ? values[1].serialized : '')) || '';
const paymentHash = (foundExistingInvoice && foundExistingInvoice.paymentHash ? foundExistingInvoice.paymentHash : (values[1] ? values[1].paymentHash : '') || '');
return sendPaymentToRouteRequestCall(req.session.selectedNode, shortChannelIds, invoice, req.body.amountMsat).then((payToRouteCallRes) => {
// eslint-disable-next-line arrow-body-style
setTimeout(() => {
return getSentInfoFromPaymentRequest(req.session.selectedNode, paymentHash).then((sentInfoCallRes) => {
const payStatus = sentInfoCallRes.length && sentInfoCallRes.length > 0 ? sentInfoCallRes[sentInfoCallRes.length - 1].status : sentInfoCallRes;
return res.status(201).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentRoute: shortChannelIds, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: payStatus });
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Sent Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentRoute: shortChannelIds, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: { error: err.error } });
});
}, 3000);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Send Payment To Route Error', req.session.selectedNode);
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentRoute: shortChannelIds, paymentHash: paymentHash, paymentDetails: {}, paymentStatus: { error: err.error } });
});
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Find Routes Error', req.session.selectedNode);
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: (foundExistingInvoice.serialized || ''), paymentRoute: '', paymentHash: '', paymentDetails: {}, paymentStatus: { error: err.error } });
});
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From List Pending Invoices Error', req.session.selectedNode);
return res.status(err.statusCode).json({ flgReusingInvoice: false, invoice: '', paymentRoute: '', paymentHash: '', paymentDetails: {}, paymentStatus: { error: err.error } });
});
};

@ -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;
};
@ -129,7 +126,8 @@ export const getPayments = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/audit';
options.form = null;
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
if (common.read_dummy_data) {
common.getDummyData('Payments', req.session.selectedNode.ln_implementation).then((data) => { res.status(200).json(arrangePayments(req.session.selectedNode, data)); });
}

@ -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 });
});
}

@ -19,7 +19,7 @@ export const getReceivedPaymentInfo = (lnServerUrl, invoice) => {
invoice.status = response.status.type;
if (response.status && response.status.type === 'received') {
invoice.amountSettled = response.status.amount ? Math.round(response.status.amount / 1000) : 0;
invoice.receivedAt = response.status.receivedAt ? Math.round(response.status.receivedAt / 1000) : 0;
invoice.receivedAt = response.status.receivedAt.unix ? response.status.receivedAt.unix : 0;
}
return invoice;
}).catch((err) => {
@ -53,28 +53,40 @@ export const getInvoice = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const listPendingInvoicesRequestCall = (selectedNode) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'List Pending Invoices..' });
options = selectedNode.options;
options.url = selectedNode.ln_server_url + '/listpendinginvoices';
options.form = { from: 0, to: (Math.round(new Date(Date.now()).getTime() / 1000)).toString() };
return new Promise((resolve, reject) => {
request.post(options).then((pendingInvoicesResponse) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Pending Invoices List ', data: pendingInvoicesResponse });
resolve(pendingInvoicesResponse);
}).catch((errRes) => {
reject(common.handleError(errRes, 'Invoices', 'List Pending Invoices Error', selectedNode));
});
});
};
export const listInvoices = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting List Invoices..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.form = {};
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
const options1 = JSON.parse(JSON.stringify(options));
options1.url = req.session.selectedNode.ln_server_url + '/listinvoices';
options1.form = {};
options1.form = { from: 0, to: tillToday };
const options2 = JSON.parse(JSON.stringify(options));
options2.url = req.session.selectedNode.ln_server_url + '/listpendinginvoices';
options2.form = {};
options2.form = { from: 0, to: tillToday };
if (common.read_dummy_data) {
return common.getDummyData('Invoices', req.session.selectedNode.ln_implementation).then((body) => {
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);
});
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => res.status(200).json(invoices));
});
}
else {
@ -84,9 +96,8 @@ 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];
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))).
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);
}).
@ -106,22 +117,30 @@ export const listInvoices = (req, res, next) => {
});
}
};
export const createInvoiceRequestCall = (selectedNode, description, amount) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
options = selectedNode.options;
options.url = selectedNode.ln_server_url + '/createinvoice';
options.form = { description: description, amountMsat: amount };
return new Promise((resolve, reject) => {
request.post(options).then((invResponse) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: invResponse });
if (invResponse.amount) {
invResponse.amount = Math.round(invResponse.amount / 1000);
}
resolve(invResponse);
}).catch((errRes) => {
reject(common.handleError(errRes, 'Invoices', 'Create Invoice Error', selectedNode));
});
});
};
export const createInvoice = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
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 + '/createinvoice';
options.form = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body });
if (body.amount) {
body.amount = Math.round(body.amount / 1000);
}
res.status(201).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Invoices', 'Create Invoice Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
createInvoiceRequestCall(req.session.selectedNode, req.body.description, req.body.amountMsat).then((invRes) => {
res.status(201).json(invRes);
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
};

@ -20,3 +20,26 @@ export const getNodes = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const findRouteBetweenNodesRequestCall = (selectedNode, amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds = [], format = 'shortChannelId') => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Find Route Between Nodes..' });
options = selectedNode.options;
options.url = selectedNode.ln_server_url + '/findroutebetweennodes';
options.form = { amountMsat: amountMsat, sourceNodeId: sourceNodeId, targetNodeId: targetNodeId, ignoreNodeIds: ignoreNodeIds, format: format };
return new Promise((resolve, reject) => {
request.post(options).then((body) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Route Lookup Between Nodes Finished', data: body });
resolve(body);
}).catch((errRes) => {
reject(common.handleError(errRes, 'Network', 'Route Lookup Between Nodes Error', selectedNode));
});
});
};
export const findRouteBetweenNodes = (req, res, next) => {
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format).then((callRes) => {
res.status(200).json(callRes);
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
};

@ -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) => {

@ -22,7 +22,7 @@ export const getSentInfoFromPaymentRequest = (selNode, payment) => {
};
export const getQueryNodes = (selNode, nodeIds) => {
options.url = selNode.ln_server_url + '/nodes';
options.form = { nodeIds: nodeIds === null || nodeIds === void 0 ? void 0 : nodeIds.reduce((acc, curr) => acc + ',' + curr) };
options.form = { nodeIds: nodeIds?.reduce((acc, curr) => acc + ',' + curr) };
return request.post(options).then((nodes) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes });
return nodes;
@ -77,16 +77,14 @@ export const queryPaymentRoute = (req, res, next) => {
};
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form });
request.post(options).then((body) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body });
if (body && body.routes && body.routes.length) {
let allRoutesNodeIds = [];
allRoutesNodeIds = (_a = body.routes) === null || _a === void 0 ? void 0 : _a.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
allRoutesNodeIds = body.routes?.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
return getQueryNodes(req.session.selectedNode, allRoutesNodeIds).then((nodesWithAlias) => {
let foundPeer = null;
body.routes.forEach((route, i) => {
var _a;
(_a = route.nodeIds) === null || _a === void 0 ? void 0 : _a.map((node, j) => {
route.nodeIds?.map((node, j) => {
foundPeer = nodesWithAlias.find((nodeWithAlias) => node === nodeWithAlias.nodeId);
body.routes[i].nodeIds[j] = { nodeId: node, alias: foundPeer ? foundPeer.alias : '' };
return node;
@ -113,7 +111,7 @@ export const getSentPaymentsInformation = (req, res, next) => {
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr === null || paymentsArr === void 0 ? void 0 : paymentsArr.map((payment) => getSentInfoFromPaymentRequest(req.session.selectedNode, payment))).
return Promise.all(paymentsArr?.map((payment) => getSentInfoFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent Information Received', data: values });
return res.status(200).json(values);
@ -128,3 +126,28 @@ export const getSentPaymentsInformation = (req, res, next) => {
return res.status(200).json([]);
}
};
export const sendPaymentToRouteRequestCall = (selectedNode, shortChannelIds, invoice, amountMsat) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
options = selectedNode.options;
options.url = selectedNode.ln_server_url + '/sendtoroute';
options.form = { shortChannelIds: shortChannelIds, amountMsat: amountMsat, invoice: invoice };
return new Promise((resolve, reject) => {
logger.log({ selectedNode: selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment To Route Options', data: options.form });
request.post(options).then((body) => {
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent To Route', data: body });
resolve(body);
}).catch((errRes) => {
reject(common.handleError(errRes, 'Payments', 'Send Payment To Route Error', selectedNode));
});
});
};
export const sendPaymentToRoute = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Send Payment To Route..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
sendPaymentToRouteRequestCall(req.session.selectedNode, req.body.shortChannelIds, req.body.invoice, req.body.amountMsat).then((callRes) => {
res.status(200).json(callRes);
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
};

@ -32,12 +32,11 @@ export const getPeers = (req, res, next) => {
peersNodeIds = peersNodeIds.substring(1);
return getFilteredNodes(req.session.selectedNode, peersNodeIds).then((peersWithAlias) => {
let foundPeer = null;
body === null || body === void 0 ? void 0 : body.map((peer) => {
body?.map((peer) => {
foundPeer = peersWithAlias.find((peerWithAlias) => peer.nodeId === peerWithAlias.nodeId);
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);
});
@ -85,13 +84,12 @@ export const connectPeer = (req, res, next) => {
peersNodeIds = peersNodeIds.substring(1);
return getFilteredNodes(req.session.selectedNode, peersNodeIds).then((peersWithAlias) => {
let foundPeer = null;
body === null || body === void 0 ? void 0 : body.map((peer) => {
body?.map((peer) => {
foundPeer = peersWithAlias.find((peerWithAlias) => peer.nodeId === peerWithAlias.nodeId);
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);
});

@ -2,6 +2,7 @@ import WebSocket from 'ws';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
import { ECLWSEventsEnum } from '../../models/ecl.model.js';
export class ECLWebSocketClient {
constructor() {
this.logger = Logger;
@ -45,15 +46,15 @@ export class ECLWebSocketClient {
}
};
this.connectWithClient = (eclWsClt) => {
var _a;
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connecting to the Eclair\'s Websocket Server..' });
const UpdatedLNServerURL = (_a = (eclWsClt.selectedNode.ln_server_url)) === null || _a === void 0 ? void 0 : _a.replace(/^http/, 'ws');
const UpdatedLNServerURL = (eclWsClt.selectedNode.ln_server_url)?.replace(/^http/, 'ws');
const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + eclWsClt.selectedNode.ln_api_password + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
eclWsClt.webSocketClient = new WebSocket(WS_LINK);
eclWsClt.webSocketClient.onopen = () => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server..' });
this.waitTime = 0.5;
this.heartbeat(eclWsClt);
};
eclWsClt.webSocketClient.onclose = (e) => {
if (eclWsClt && eclWsClt.selectedNode && eclWsClt.selectedNode.ln_implementation === 'ECL') {
@ -67,9 +68,11 @@ export class ECLWebSocketClient {
eclWsClt.webSocketClient.onmessage = (msg) => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
msg['source'] = 'ECL';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
if (msg.type && msg.type !== ECLWSEventsEnum.PAY_RELAYED && msg.type !== ECLWSEventsEnum.PAY_SETTLING_ONCHAIN && msg.type !== ECLWSEventsEnum.ONION_MESSAGE_RECEIVED) {
msg['source'] = 'ECL';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
}
};
eclWsClt.webSocketClient.onerror = (err) => {
if (eclWsClt.selectedNode.ln_version === '' || !eclWsClt.selectedNode.ln_version || this.common.isVersionCompatible(eclWsClt.selectedNode.ln, '0.5.0')) {
@ -105,6 +108,19 @@ export class ECLWebSocketClient {
newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
this.webSocketClients[clientIdx] = newClient;
};
this.heartbeat = (eclWsClt) => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Websocket Server Heartbeat..' });
if (!eclWsClt.webSocketClient) {
return;
}
if (eclWsClt.webSocketClient.readyState !== 1) {
return;
}
eclWsClt.webSocketClient.ping();
setTimeout(() => {
this.heartbeat(eclWsClt);
}, 59 * 1000);
};
this.wsServer.eventEmitterECL.on('CONNECT', (nodeIndex) => {
this.connect(this.common.findNode(+nodeIndex));
});

@ -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) => {
@ -28,17 +28,15 @@ export const getAllChannels = (req, res, next) => {
let remote = 0;
let total = 0;
request(options).then((body) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Channels List Received', data: body });
if (body.channels) {
return Promise.all((_a = body.channels) === null || _a === void 0 ? void 0 : _a.map((channel) => {
return Promise.all(body.channels?.map((channel) => {
local = (channel.local_balance) ? +channel.local_balance : 0;
remote = (channel.remote_balance) ? +channel.remote_balance : 0;
total = local + remote;
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) => {
@ -65,22 +63,21 @@ export const getPendingChannels = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/pending';
options.qs = req.query;
request(options).then((body) => {
var _a, _b, _c, _d;
if (!body.total_limbo_balance) {
body.total_limbo_balance = 0;
}
const promises = [];
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)));
body.pending_open_channels?.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)));
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) {
(_d = body.waiting_close_channels) === null || _d === void 0 ? void 0 : _d.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
body.waiting_close_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
return Promise.all(promises).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Pending Channels List Received', data: body });
@ -104,13 +101,11 @@ export const getClosedChannels = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/closed';
options.qs = req.query;
request(options).then((body) => {
var _a;
if (body.channels && body.channels.length > 0) {
return Promise.all((_a = body.channels) === null || _a === void 0 ? void 0 : _a.map((channel) => {
return Promise.all(body.channels?.map((channel) => {
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 +156,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;
@ -181,6 +176,7 @@ export const postTransactions = (req, res, next) => {
options.form = JSON.stringify(options.form);
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
request.post(options).then((body) => {
body = body.result ? body.result : body;
if (body.payment_error) {
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -195,7 +191,6 @@ export const postTransactions = (req, res, next) => {
});
};
export const closeChannel = (req, res, next) => {
var _a;
try {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
if (!req.session.selectedNode) {
@ -206,7 +201,7 @@ export const closeChannel = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const channelpoint = (_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '/');
const channelpoint = req.params.channelPoint?.replace(':', '/');
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/' + channelpoint + '?force=' + req.query.force;
if (req.query.target_conf) {
options.url = options.url + '&target_conf=' + req.query.target_conf;

@ -16,13 +16,12 @@ function getFilesList(channelBackupPath, callback) {
}
if (files && files.length > 0) {
files.forEach((file) => {
var _a;
if (!file.includes('.restored')) {
if (file.toLowerCase() === 'channel-all.bak' || file.toLowerCase() === 'backup-channel-all.bak') {
all_restore_exists = true;
}
else {
files_list.push({ channel_point: (_a = file.substring(8, file.length - 4)) === null || _a === void 0 ? void 0 : _a.replace('-', ':') });
files_list.push({ channel_point: file.substring(8, file.length - 4)?.replace('-', ':') });
}
}
});
@ -32,7 +31,6 @@ function getFilesList(channelBackupPath, callback) {
});
}
export const getBackup = (req, res, next) => {
var _a, _b;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'ChannelBackup', msg: 'Getting Channel Backup..' });
options = common.getOptions(req);
if (options.error) {
@ -46,9 +44,9 @@ export const getBackup = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup';
}
else {
channel_backup_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + ((_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak';
channel_backup_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
message = 'Channel Backup Successful.';
const channelpoint = (_b = req.params.channelPoint) === null || _b === void 0 ? void 0 : _b.replace(':', '/');
const channelpoint = req.params.channelPoint?.replace(':', '/');
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup/' + channelpoint;
const exists = fs.existsSync(channel_backup_file);
if (exists) {
@ -83,7 +81,6 @@ export const getBackup = (req, res, next) => {
});
};
export const postBackupVerify = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'ChannelBackup', msg: 'Verifying Channel Backup..' });
options = common.getOptions(req);
if (options.error) {
@ -119,7 +116,7 @@ export const postBackupVerify = (req, res, next) => {
}
else {
message = 'Channel Verify Successful.';
channel_verify_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + ((_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak';
channel_verify_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
const exists = fs.existsSync(channel_verify_file);
if (exists) {
verify_backup = fs.readFileSync(channel_verify_file, 'utf-8');
@ -144,7 +141,6 @@ export const postBackupVerify = (req, res, next) => {
}
};
export const postRestore = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'ChannelBackup', msg: 'Restoring Channel Backup..' });
options = common.getOptions(req);
if (options.error) {
@ -192,7 +188,7 @@ export const postRestore = (req, res, next) => {
}
else {
message = 'Channel Restore Successful.';
channel_restore_file = req.session.selectedNode.channel_backup_path + sep + 'restore' + sep + 'channel-' + ((_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak';
channel_restore_file = req.session.selectedNode.channel_backup_path + sep + 'restore' + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
const exists = fs.existsSync(channel_restore_file);
if (exists) {
restore_backup = fs.readFileSync(channel_restore_file, 'utf-8');

@ -21,11 +21,10 @@ export const getFees = (req, res, next) => {
const week_start_time = current_time - 604800;
const day_start_time = current_time - 86400;
return getAllForwardingEvents(req, month_start_time, current_time, 0, 'fees', (history) => {
var _a, _b, _c;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Forwarding History Received', data: history });
const daily_sum = (_a = history.forwarding_events) === null || _a === void 0 ? void 0 : _a.reduce((acc, curr) => ((curr.timestamp >= day_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const weekly_sum = (_b = history.forwarding_events) === null || _b === void 0 ? void 0 : _b.reduce((acc, curr) => ((curr.timestamp >= week_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const monthly_sum = (_c = history.forwarding_events) === null || _c === void 0 ? void 0 : _c.reduce((acc, curr) => [(acc[0] + 1), (acc[1] + +curr.fee_msat)], [0, 0]);
const daily_sum = history.forwarding_events?.reduce((acc, curr) => ((curr.timestamp >= day_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const weekly_sum = history.forwarding_events?.reduce((acc, curr) => ((curr.timestamp >= week_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const monthly_sum = history.forwarding_events?.reduce((acc, curr) => [(acc[0] + 1), (acc[1] + +curr.fee_msat)], [0, 0]);
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Daily Sum (Transactions, Fee)', data: daily_sum });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Weekly Sum (Transactions, Fee)', data: weekly_sum });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Monthly Sum (Transactions, Fee)', data: monthly_sum });

@ -9,7 +9,6 @@ const common = Common;
const lndWsClient = LNDWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting LND Node Information..' });
common.logEnvVariables(req);
common.setOptions(req);
@ -26,7 +25,7 @@ export const getInfo = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
else {
(_a = common.nodes) === null || _a === void 0 ? void 0 : _a.map((node) => {
common.nodes?.map((node) => {
if (node.ln_implementation === 'LND') {
common.getAllNodeAllChannelBackup(node);
}
@ -45,7 +44,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);
}

@ -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..' });
@ -84,13 +84,11 @@ export const getQueryRoutes = (req, res, next) => {
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Graph', msg: 'Query Routes URL', data: options.url });
request(options).then((body) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Graph', msg: 'Query Routes Received', data: body });
if (body.routes && body.routes.length && body.routes.length > 0 && body.routes[0].hops && body.routes[0].hops.length && body.routes[0].hops.length > 0) {
return Promise.all((_a = body.routes[0].hops) === null || _a === void 0 ? void 0 : _a.map((hop) => getAliasFromPubkey(req.session.selectedNode, hop.pub_key))).
return Promise.all(body.routes[0].hops?.map((hop) => getAliasFromPubkey(req.session.selectedNode, hop.pub_key))).
then((values) => {
var _a;
(_a = body.routes[0].hops) === null || _a === void 0 ? void 0 : _a.map((hop, i) => {
body.routes[0].hops?.map((hop, i) => {
hop.hop_sequence = i + 1;
hop.pubkey_alias = values[i];
return hop;
@ -150,7 +148,7 @@ export const getAliasesForPubkeys = (req, res, next) => {
}
if (req.query.pubkeys) {
const pubkeyArr = req.query.pubkeys.split(',');
return Promise.all(pubkeyArr === null || pubkeyArr === void 0 ? void 0 : pubkeyArr.map((pubkey) => getAliasFromPubkey(req.session.selectedNode, pubkey))).
return Promise.all(pubkeyArr?.map((pubkey) => getAliasFromPubkey(req.session.selectedNode, pubkey))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Node Alias', data: values });
res.status(200).json(values);

@ -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 {

@ -34,7 +34,7 @@ export const decodePayments = (req, res, next) => {
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr === null || paymentsArr === void 0 ? void 0 : paymentsArr.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
return Promise.all(paymentsArr?.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'PayRequest', msg: 'Payment List Decoded', data: values });
res.status(200).json(values);
@ -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);

@ -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;
});
};
@ -25,11 +25,7 @@ export const getPeers = (req, res, next) => {
request(options).then((body) => {
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');
}
return Promise.all(peers?.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body.peers });
res.status(200).json(body.peers);
});
@ -54,9 +50,8 @@ export const postPeer = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/peers';
request(options).then((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) => {
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 });
}

@ -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 {

@ -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);

@ -42,8 +42,7 @@ export class LNDWebSocketClient {
});
};
this.subscribeToInvoice = (options, selectedNode, rHash) => {
var _a;
rHash = (_a = rHash === null || rHash === void 0 ? void 0 : rHash.replace(/\+/g, '-')) === null || _a === void 0 ? void 0 : _a.replace(/[/]/g, '_');
rHash = rHash?.replace(/\+/g, '-')?.replace(/[/]/g, '_');
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Invoice ' + rHash + ' ..' });
options.url = selectedNode.ln_server_url + '/v2/invoices/subscribe/' + rHash;
request(options).then((msg) => {

@ -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';
@ -135,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';
@ -146,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';
@ -227,7 +231,6 @@ export const getConfig = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'Node Type', data: req.params.nodeType });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'File Path', data: confFile });
fs.readFile(confFile, 'utf8', (errRes, data) => {
var _a;
if (errRes) {
const errMsg = 'Reading Config Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
@ -240,27 +243,26 @@ export const getConfig = (req, res, next) => {
}
else {
fileFormat = 'INI';
data = data === null || data === void 0 ? void 0 : data.replace('color=#', 'color=');
data = data?.replace('color=#', 'color=');
jsonConfig = ini.parse(data);
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);
}
}
jsonConfig = maskPasswords(jsonConfig);
const responseJSON = (fileFormat === 'JSON') ? jsonConfig : (_a = ini.stringify(jsonConfig)) === null || _a === void 0 ? void 0 : _a.replace('color=\\#', 'color=#');
const responseJSON = (fileFormat === 'JSON') ? jsonConfig : ini.stringify(jsonConfig)?.replace('color=\\#', 'color=#');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Configuration File Data Received', data: responseJSON });
res.status(200).json({ format: fileFormat, data: responseJSON });
}
});
};
export const getFile = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting File..' });
const file = req.query.path ? req.query.path : (req.session.selectedNode.channel_backup_path + sep + 'channel-' + ((_a = req.query.channel) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak');
const file = req.query.path ? req.query.path : (req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.query.channel?.replace(':', '-') + '.bak');
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'Channel Point', data: req.query.channel });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'File Path', data: file });
fs.readFile(file, 'utf8', (errRes, data) => {
@ -379,7 +381,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]] = '********************';
}
}

@ -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');

@ -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);

@ -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 });
});
};

@ -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, enable_peerswap) {
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,6 +20,7 @@ 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;
@ -36,10 +37,11 @@ export class AuthenticationConfiguration {
}
}
export class NodeSettingsConfiguration {
constructor(userPersona, themeMode, themeColor, fiatConversion, currencyUnit, bitcoindConfigPath, logLevel, lnServerUrl, swapServerUrl, boltzServerUrl, channelBackupPath, enableOffers, enablePeerswap) {
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;

@ -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["ISSUER"] = "issuer";
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, issuer, description, lastUpdatedAt) {
this.bolt12 = bolt12;
this.amountmSat = amountmSat;
this.amountMSat = amountMSat;
this.title = title;
this.vendor = vendor;
this.issuer = issuer;
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 = { ...OfferFieldsEnum, ...PageSettingsFieldsEnum, ...TableSettingsFieldsEnum };
export const LNDCollection = [CollectionsEnum.PAGE_SETTINGS];
export const ECLCollection = [CollectionsEnum.PAGE_SETTINGS];
export const CLNCollection = [CollectionsEnum.PAGE_SETTINGS, CollectionsEnum.OFFERS];

@ -0,0 +1,12 @@
export var ECLWSEventsEnum;
(function (ECLWSEventsEnum) {
ECLWSEventsEnum["PAY_RECEIVED"] = "payment-received";
ECLWSEventsEnum["PAY_RELAYED"] = "payment-relayed";
ECLWSEventsEnum["PAY_SENT"] = "payment-sent";
ECLWSEventsEnum["PAY_SETTLING_ONCHAIN"] = "payment-settling-onchain";
ECLWSEventsEnum["PAY_FAILED"] = "payment-failed";
ECLWSEventsEnum["CHANNEL_OPENED"] = "channel-opened";
ECLWSEventsEnum["CHANNEL_STATE_CHANGED"] = "channel-state-changed";
ECLWSEventsEnum["CHANNEL_CLOSED"] = "channel-closed";
ECLWSEventsEnum["ONION_MESSAGE_RECEIVED"] = "onion-message-received";
})(ECLWSEventsEnum || (ECLWSEventsEnum = {}));

@ -1,9 +1,10 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
import { listChannels, listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
const router = Router();
router.get('/listChannels', isAuthenticated, listChannels);
router.get('/listPeerChannels', isAuthenticated, listPeerChannels);
router.post('/', isAuthenticated, openChannel);
router.post('/setChannelFee', isAuthenticated, setChannelFee);
router.delete('/:channelId', isAuthenticated, closeChannel);

@ -1,11 +1,12 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel } from '../../controllers/eclair/channels.js';
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel, circularRebalance } from '../../controllers/eclair/channels.js';
const router = Router();
router.get('/', isAuthenticated, getChannels);
router.get('/stats', isAuthenticated, getChannelStats);
router.post('/', isAuthenticated, openChannel);
router.post('/updateRelayFee', isAuthenticated, updateChannelRelayFee);
router.post('/circularRebalance', circularRebalance);
router.delete('/', isAuthenticated, closeChannel);
export default router;

@ -1,7 +1,8 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getNodes } from '../../controllers/eclair/network.js';
import { getNodes, findRouteBetweenNodes } from '../../controllers/eclair/network.js';
const router = Router();
router.get('/nodes/:id', isAuthenticated, getNodes);
router.get('/routebetweennodes', isAuthenticated, findRouteBetweenNodes);
export default router;

@ -1,10 +1,11 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment } from '../../controllers/eclair/payments.js';
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment, sendPaymentToRoute } from '../../controllers/eclair/payments.js';
const router = Router();
router.get('/route/', isAuthenticated, queryPaymentRoute);
router.get('/decode/:invoice', isAuthenticated, decodePayment);
router.post('/getsentinfos', isAuthenticated, getSentPaymentsInformation);
router.post('/sendtoroute', isAuthenticated, sendPaymentToRoute);
router.post('/', isAuthenticated, postPayment);
export default router;

@ -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);

@ -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;

@ -12,7 +12,6 @@ import clnRoutes from '../routes/cln/index.js';
import eclRoutes from '../routes/eclair/index.js';
import { Common } from './common.js';
import { Logger } from './logger.js';
import { Config } from './config.js';
import { CLWSClient } from '../controllers/cln/webSocketClient.js';
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
@ -22,15 +21,11 @@ export class ExpressApplication {
this.app = express();
this.logger = Logger;
this.common = Common;
this.config = Config;
this.eclWsClient = ECLWSClient;
this.clWsClient = CLWSClient;
this.lndWsClient = LNDWSClient;
this.directoryName = dirname(fileURLToPath(import.meta.url));
this.getApp = () => this.app;
this.loadConfiguration = () => {
this.config.setServerConfiguration();
};
this.setCORS = () => { CORS.mount(this.app); };
this.setCSRF = () => { CSRF.mount(this.app); };
this.setApplicationRoutes = () => {
@ -41,13 +36,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() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : ''); // 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) => {
@ -80,7 +76,6 @@ export class ExpressApplication {
this.app.use(cookieParser(this.common.secret_key));
this.app.use(bodyParser.json({ limit: '25mb' }));
this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' }));
this.loadConfiguration();
this.setCORS();
this.setCSRF();
this.setApplicationRoutes();

@ -18,16 +18,14 @@ export const isAuthenticated = (req, res, next) => {
}
};
export const verifyWSUser = (info, next) => {
var _a;
const headers = JSON.parse(JSON.stringify(info.req.headers));
const protocols = !info.req.headers['sec-websocket-protocol'] ? [] : (_a = info.req.headers['sec-websocket-protocol'].split(',')) === null || _a === void 0 ? void 0 : _a.map((s) => s.trim());
const protocols = !info.req.headers['sec-websocket-protocol'] ? [] : info.req.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
const jwToken = (protocols && protocols.length > 0) ? protocols[0] : '';
if (!jwToken || jwToken === '') {
next(false, 401, 'Authentication Failed! Please Login First!');
}
else {
jwt.verify(jwToken, common.secret_key, (verificationErr) => {
var _a, _b, _c;
if (verificationErr) {
next(false, 401, 'Authentication Failed! Please Login First!');
}
@ -42,7 +40,7 @@ export const verifyWSUser = (info, next) => {
}
let cookies = null;
try {
cookies = '{"' + ((_c = (_b = (_a = headers.cookie) === null || _a === void 0 ? void 0 : _a.replace(/ /g, '')) === null || _b === void 0 ? void 0 : _b.replace(/;/g, '","').trim()) === null || _c === void 0 ? void 0 : _c.replace(/[=]/g, '":"')) + '"}';
cookies = '{"' + headers.cookie?.replace(/ /g, '')?.replace(/;/g, '","').trim()?.replace(/[=]/g, '":"') + '"}';
updatedReq['cookies'] = JSON.parse(cookies);
}
catch (err) {

@ -11,7 +11,8 @@ export class CommonService {
this.initSelectedNode = null;
this.rtl_conf_file_path = '';
this.port = 3000;
this.host = null;
this.host = '';
this.db_directory_path = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
this.rtl_pass = '';
this.flg_allow_password_update = true;
this.rtl_secret2fa = '';
@ -24,7 +25,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,
@ -198,18 +202,17 @@ export class CommonService {
};
this.newestOnTop = (array, key, value) => {
const newlyAddedRecord = array.splice(array.findIndex((item) => item[key] === value), 1);
array === null || array === void 0 ? void 0 : array.unshift(newlyAddedRecord[0]);
array?.unshift(newlyAddedRecord[0]);
return array;
};
this.camelCase = (str) => { var _a, _b; return (_b = (_a = str === null || str === void 0 ? void 0 : str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (word.toUpperCase()))) === null || _a === void 0 ? void 0 : _a.replace(/\s+/g, '')) === null || _b === void 0 ? void 0 : _b.replace(/-/g, ' '); };
this.camelCase = (str) => str?.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (word.toUpperCase()))?.replace(/\s+/g, '')?.replace(/-/g, ' ');
this.titleCase = (str) => {
var _a, _b;
if (str.indexOf('!\n') > 0 || str.indexOf('.\n') > 0) {
return (_a = str.split('\n')) === null || _a === void 0 ? void 0 : _a.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + '\n', '');
return str.split('\n')?.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + '\n', '');
}
else {
if (str.indexOf(' ') > 0) {
return (_b = str.split(' ')) === null || _b === void 0 ? void 0 : _b.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + ' ', '');
return str.split(' ')?.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + ' ', '');
}
else {
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
@ -217,7 +220,11 @@ export class CommonService {
}
};
this.handleError = (errRes, fileName, errMsg, selectedNode) => {
const err = JSON.parse(JSON.stringify(errRes));
let err = JSON.parse(JSON.stringify(errRes));
if (err && err.error && Object.keys(err.error).length === 0 && errRes.error && (errRes.error.stack || errRes.error.message)) {
errRes.error = errRes.error.stack || errRes.error.message;
err = JSON.parse(JSON.stringify(errRes));
}
if (!selectedNode) {
selectedNode = this.initSelectedNode;
}
@ -252,17 +259,30 @@ 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 = {
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 :
(err.error && err.error.error && typeof err.error.error === 'string') ? err.error.error :
(err.error && err.error.error && err.error.error.message && typeof err.error.error.message === 'string') ? err.error.error.message :
(err.error && err.error.message && typeof err.error.message === 'string') ? err.error.message :
(err.error && typeof err.error === 'string') ? err.error :
(err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error')
};
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : err) });
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 :
(err.error && err.error.error && typeof err.error.error === 'string') ? err.error.error :
(err.error && err.error.error && err.error.error.message && typeof err.error.error.message === 'string') ? err.error.error.message :
(err.error && err.error.message && typeof err.error.message === 'string') ? err.error.message :
(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 && err.message.indexOf('Authentication Error') < 0 && err.name && 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()) ||
@ -301,7 +321,7 @@ export class CommonService {
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading cookie: \n' + err });
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading cookie: \n' + err });
throw new Error(err);
}
}
@ -313,7 +333,7 @@ export class CommonService {
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err });
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading the cookie: \n' + err });
throw new Error(err);
}
}
@ -329,9 +349,8 @@ export class CommonService {
}
};
this.createDirectory = (directoryName) => {
var _a;
const initDir = isAbsolute(directoryName) ? sep : '';
(_a = directoryName.split(sep)) === null || _a === void 0 ? void 0 : _a.reduce((parentDir, childDir) => {
directoryName.split(sep)?.reduce((parentDir, childDir) => {
const curDir = resolve(parentDir, childDir);
try {
if (!fs.existsSync(curDir)) {
@ -400,9 +419,8 @@ export class CommonService {
});
};
this.isVersionCompatible = (currentVersion, checkVersion) => {
var _a;
if (currentVersion) {
const versionsArr = ((_a = currentVersion.trim()) === null || _a === void 0 ? void 0 : _a.replace('v', '').split('-')[0].split('.')) || [];
const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || [];
const checkVersionsArr = checkVersion.split('.');
return (+versionsArr[0] > +checkVersionsArr[0]) ||
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) ||
@ -416,6 +434,7 @@ export class CommonService {
if (selNode && selNode.index) {
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'PORT: ' + this.port });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'HOST: ' + this.host });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DB_DIRECTORY_PATH: ' + this.db_directory_path });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.rtl_sso });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + selNode.index });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'INDEX: ' + selNode.index });

@ -20,31 +20,37 @@ export class ConfigService {
let macaroonPath = '';
let configPath = '';
let channelBackupPath = '';
let dbPath = '';
switch (this.platform) {
case 'win32':
macaroonPath = homeDir + '\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet';
configPath = homeDir + '\\AppData\\Local\\Lnd\\lnd.conf';
channelBackupPath = homeDir + '\\backup\\node-1';
dbPath = homeDir + '\\database\\node-1';
break;
case 'darwin':
macaroonPath = homeDir + '/Library/Application Support/Lnd/data/chain/bitcoin/mainnet';
configPath = homeDir + '/Library/Application Support/Lnd/lnd.conf';
channelBackupPath = homeDir + '/backup/node-1';
dbPath = homeDir + '/database/node-1';
break;
case 'linux':
macaroonPath = homeDir + '/.lnd/data/chain/bitcoin/mainnet';
configPath = homeDir + '/.lnd/lnd.conf';
channelBackupPath = homeDir + '/backup/node-1';
dbPath = homeDir + '/database/node-1';
break;
default:
macaroonPath = '';
configPath = '';
channelBackupPath = '';
dbPath = '';
break;
}
const configData = {
port: '3000',
defaultNodeIndex: 1,
dbDirectoryPath: dbPath,
SSO: {
rtlSSO: 0,
rtlCookiePath: '',
@ -65,13 +71,14 @@ export class ConfigService {
themeColor: 'PURPLE',
channelBackupPath: channelBackupPath,
logLevel: 'ERROR',
lnServerUrl: 'https://localhost:8080',
fiatConversion: false
lnServerUrl: 'https://127.0.0.1:8080',
fiatConversion: false,
unannouncedChannels: false
}
}
]
};
if (+process.env.RTL_SSO === 0) {
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || configData.SSO.rtlSSO === 0) {
configData['multiPass'] = 'password';
}
return configData;
@ -88,7 +95,7 @@ export class ConfigService {
};
this.updateLogByLevel = () => {
let updateLogFlag = false;
this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..');
this.common.rtl_conf_file_path = process?.env?.RTL_CONFIG_PATH ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
try {
const RTLConfFile = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
@ -108,9 +115,9 @@ export class ConfigService {
}
};
this.validateNodeConfig = (config) => {
if ((+process.env.RTL_SSO === 0) || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') {
this.common.rtl_pass = this.hash.update(process.env.APP_PASSWORD).digest('hex');
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || (typeof process?.env?.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
this.common.rtl_pass = this.hash.update(process?.env?.APP_PASSWORD).digest('hex');
this.common.flg_allow_password_update = false;
}
else if (config.multiPassHashed && config.multiPassHashed !== '') {
@ -125,23 +132,24 @@ export class ConfigService {
this.common.rtl_secret2fa = config.secret2fa;
}
else {
if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') {
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
this.errMsg = this.errMsg + '\nRTL Password cannot be set with SSO. Please set SSO as 0 or remove password.';
}
}
this.common.port = (process.env.PORT) ? this.normalizePort(process.env.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
this.common.host = (process.env.HOST) ? process.env.HOST : (config.host) ? config.host : null;
this.common.port = (process?.env?.PORT) ? this.normalizePort(process?.env?.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
this.common.host = (process?.env?.HOST) ? process?.env?.HOST : (config.host) ? config.host : null;
this.common.db_directory_path = (process?.env?.DB_DIRECTORY_PATH) ? process?.env?.DB_DIRECTORY_PATH : (config.dbDirectoryPath) ? config.dbDirectoryPath : join(dirname(fileURLToPath(import.meta.url)), '..', '..');
if (config.nodes && config.nodes.length > 0) {
config.nodes.forEach((node, idx) => {
this.common.nodes[idx] = {};
this.common.nodes[idx].index = node.index;
this.common.nodes[idx].ln_node = node.lnNode;
this.common.nodes[idx].ln_implementation = (process.env.LN_IMPLEMENTATION) ? process.env.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
this.common.nodes[idx].ln_implementation = (process?.env?.LN_IMPLEMENTATION) ? process?.env?.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
if (this.common.nodes[idx].ln_implementation === 'CLT') {
this.common.nodes[idx].ln_implementation = 'CLN';
}
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process.env.MACAROON_PATH && process.env.MACAROON_PATH.trim() !== '') {
this.common.nodes[idx].macaroon_path = process.env.MACAROON_PATH;
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH;
}
else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath;
@ -150,8 +158,8 @@ export class ConfigService {
this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!';
}
if (this.common.nodes[idx].ln_implementation === 'ECL') {
if (process.env.LN_API_PASSWORD) {
this.common.nodes[idx].ln_api_password = process.env.LN_API_PASSWORD;
if (process?.env?.LN_API_PASSWORD) {
this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD;
}
else if (node.Authentication && node.Authentication.lnApiPassword) {
this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword;
@ -160,8 +168,8 @@ export class ConfigService {
this.common.nodes[idx].ln_api_password = '';
}
}
if (process.env.CONFIG_PATH) {
this.common.nodes[idx].config_path = process.env.CONFIG_PATH;
if (process?.env?.CONFIG_PATH) {
this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH;
}
else if (node.Authentication && node.Authentication.configPath) {
this.common.nodes[idx].config_path = node.Authentication.configPath;
@ -171,10 +179,10 @@ export class ConfigService {
}
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '' && this.common.nodes[idx].config_path !== '') {
try {
const exists = fs.existsSync(this.common.nodes[idx].config_path);
const exists = fs.existsSync(this.common.nodes[idx].config_path || '');
if (exists) {
try {
const configFile = fs.readFileSync(this.common.nodes[idx].config_path, 'utf-8');
const configFile = fs.readFileSync((this.common.nodes[idx].config_path || ''), 'utf-8');
const iniParsed = ini.parse(configFile);
this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password;
}
@ -193,11 +201,11 @@ export class ConfigService {
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '') {
this.errMsg = this.errMsg + '\nPlease set config path Or api password for node index ' + node.index + ' in RTL-Config.json! It is mandatory for Eclair authentication!';
}
if (process.env.LN_SERVER_URL && process.env.LN_SERVER_URL.trim() !== '') {
this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') ? process.env.LN_SERVER_URL.slice(0, -3) : process.env.LN_SERVER_URL;
if (process?.env?.LN_SERVER_URL && process?.env?.LN_SERVER_URL.trim() !== '') {
this.common.nodes[idx].ln_server_url = process?.env?.LN_SERVER_URL.endsWith('/v1') ? process?.env?.LN_SERVER_URL.slice(0, -3) : process?.env?.LN_SERVER_URL;
}
else if (process.env.LND_SERVER_URL && process.env.LND_SERVER_URL.trim() !== '') {
this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') ? process.env.LND_SERVER_URL.slice(0, -3) : process.env.LND_SERVER_URL;
else if (process?.env?.LND_SERVER_URL && process?.env?.LND_SERVER_URL.trim() !== '') {
this.common.nodes[idx].ln_server_url = process?.env?.LND_SERVER_URL.endsWith('/v1') ? process?.env?.LND_SERVER_URL.slice(0, -3) : process?.env?.LND_SERVER_URL;
}
else if (node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') {
this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') ? node.Settings.lnServerUrl.slice(0, -3) : node.Settings.lnServerUrl;
@ -211,14 +219,15 @@ 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) {
this.common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD';
}
if (process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') {
this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL;
this.common.nodes[idx].swap_macaroon_path = process.env.SWAP_MACAROON_PATH;
if (process?.env?.SWAP_SERVER_URL && process?.env?.SWAP_SERVER_URL.trim() !== '') {
this.common.nodes[idx].swap_server_url = process?.env?.SWAP_SERVER_URL.endsWith('/v1') ? process?.env?.SWAP_SERVER_URL.slice(0, -3) : process?.env?.SWAP_SERVER_URL;
this.common.nodes[idx].swap_macaroon_path = process?.env?.SWAP_MACAROON_PATH;
}
else if (node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') {
this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl;
@ -228,9 +237,9 @@ export class ConfigService {
this.common.nodes[idx].swap_server_url = '';
this.common.nodes[idx].swap_macaroon_path = '';
}
if (process.env.BOLTZ_SERVER_URL && process.env.BOLTZ_SERVER_URL.trim() !== '') {
this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') ? process.env.BOLTZ_SERVER_URL.slice(0, -3) : process.env.BOLTZ_SERVER_URL;
this.common.nodes[idx].boltz_macaroon_path = process.env.BOLTZ_MACAROON_PATH;
if (process?.env?.BOLTZ_SERVER_URL && process?.env?.BOLTZ_SERVER_URL.trim() !== '') {
this.common.nodes[idx].boltz_server_url = process?.env?.BOLTZ_SERVER_URL.endsWith('/v1') ? process?.env?.BOLTZ_SERVER_URL.slice(0, -3) : process?.env?.BOLTZ_SERVER_URL;
this.common.nodes[idx].boltz_macaroon_path = process?.env?.BOLTZ_MACAROON_PATH;
}
else if (node.Settings.boltzServerUrl && node.Settings.boltzServerUrl.trim() !== '') {
this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') ? node.Settings.boltzServerUrl.slice(0, -3) : node.Settings.boltzServerUrl;
@ -240,10 +249,10 @@ export class ConfigService {
this.common.nodes[idx].boltz_server_url = '';
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;
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 {
this.common.createDirectory(this.common.nodes[idx].channel_backup_path);
const exists = fs.existsSync(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak');
@ -268,14 +277,14 @@ export class ConfigService {
this.common.nodes[idx].log_file = this.common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log';
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) });
const log_file = this.common.nodes[idx].log_file;
if (fs.existsSync(log_file)) {
fs.writeFile(log_file, '', () => { });
if (fs.existsSync(log_file || '')) {
fs.writeFile((log_file || ''), '', () => { });
}
else {
try {
const directoryName = dirname(log_file);
const directoryName = dirname(log_file || '');
this.common.createDirectory(directoryName);
const createStream = fs.createWriteStream(log_file);
const createStream = fs.createWriteStream(log_file || '');
createStream.end();
}
catch (err) {
@ -290,14 +299,14 @@ export class ConfigService {
}
};
this.setSSOParams = (config) => {
if (process.env.RTL_SSO) {
this.common.rtl_sso = +process.env.RTL_SSO;
if (process?.env?.RTL_SSO) {
this.common.rtl_sso = +process?.env?.RTL_SSO;
}
else if (config.SSO && config.SSO.rtlSSO) {
this.common.rtl_sso = config.SSO.rtlSSO;
}
if (process.env.RTL_COOKIE_PATH) {
this.common.rtl_cookie_path = process.env.RTL_COOKIE_PATH;
if (process?.env?.RTL_COOKIE_PATH) {
this.common.rtl_cookie_path = process?.env?.RTL_COOKIE_PATH;
}
else if (config.SSO && config.SSO.rtlCookiePath) {
this.common.rtl_cookie_path = config.SSO.rtlCookiePath;
@ -305,8 +314,8 @@ export class ConfigService {
else {
this.common.rtl_cookie_path = '';
}
if (process.env.LOGOUT_REDIRECT_LINK) {
this.common.logout_redirect_link = process.env.LOGOUT_REDIRECT_LINK;
if (process?.env?.LOGOUT_REDIRECT_LINK) {
this.common.logout_redirect_link = process?.env?.LOGOUT_REDIRECT_LINK;
}
else if (config.SSO && config.SSO.logoutRedirectLink) {
this.common.logout_redirect_link = config.SSO.logoutRedirectLink;
@ -322,15 +331,15 @@ export class ConfigService {
};
this.setSelectedNode = (config) => {
if (config.defaultNodeIndex) {
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex);
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex) || {};
}
else {
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index);
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index) || {};
}
};
this.setServerConfiguration = () => {
try {
this.common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..');
this.common.rtl_conf_file_path = (process?.env?.RTL_CONFIG_PATH) ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
const confFileFullPath = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
if (!fs.existsSync(confFileFullPath)) {
fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig()));
@ -345,6 +354,7 @@ export class ConfigService {
throw new Error(err);
}
};
this.setServerConfiguration();
}
}
export const Config = new ConfigService();

@ -1,43 +1,77 @@
import * as fs from 'fs';
import { join, dirname, sep } from 'path';
import { fileURLToPath } from 'url';
import { join, sep } from 'path';
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;
this.logger = Logger;
this.dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
this.dbDirectory = join(this.common.db_directory_path, '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,23 +98,17 @@ export class DatabaseService {
}
updatedDocument = foundDoc;
}
const validationRes = this.validateDocument(CollectionsEnum.OFFERS, updatedDocument);
if (!validationRes.isValid) {
reject(validationRes.error);
if (foundDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(foundDocIdx, 1, updatedDocument);
}
else {
if (foundDocIdx > -1) {
this.nodeDatabase[selectedNode.index].data[collectionName].splice(foundDocIdx, 1, updatedDocument);
}
else {
if (!this.nodeDatabase[selectedNode.index].data[collectionName]) {
this.nodeDatabase[selectedNode.index].data[collectionName] = [];
}
this.nodeDatabase[selectedNode.index].data[collectionName].push(updatedDocument);
if (!this.nodeDatabase[selectedNode.index].data[collectionName]) {
this.nodeDatabase[selectedNode.index].data[collectionName] = [];
}
this.saveDatabase(+selectedNode.index);
resolve(updatedDocument);
this.nodeDatabase[selectedNode.index].data[collectionName].push(updatedDocument);
}
this.saveDatabase(selectedNode, collectionName);
resolve(updatedDocument);
}
catch (errRes) {
reject(errRes);
@ -105,7 +133,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 +146,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 +154,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 +165,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) {
this.saveDatabase(nodeIndex);
this.nodeDatabase[nodeIndex] = null;
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];
}
}
}
}
}
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);
}
// For backward compatibility End
this.insertSession(id);
}
fetchData() {
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 ? 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 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) {
this.logger.log({ selectedNode: this.selNode, level: 'ERROR', fileName: 'Database', msg: 'Rename Other Implementation DB Error', error: err });
}
try {
const dataFromFile = fs.readFileSync(this.dbFile, 'utf-8');
return !dataFromFile ? null : JSON.parse(dataFromFile);
const dataFromFile = fs.readFileSync(collectionFilePath, 'utf-8');
const dataObj = !dataFromFile ? null : JSON.parse(dataFromFile);
return dataObj;
}
catch (err) {
return new Error('Database Read Error ' + JSON.stringify(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();

@ -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';

@ -31,7 +31,6 @@ export class RTLWebSocketServer {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connecting Websocket Server..' });
this.webSocketServer = new WebSocketServer({ noServer: true, path: this.common.baseHref + '/api/ws', verifyClient: (process.env.NODE_ENV === 'development') ? null : verifyWSUser });
httpServer.on('upgrade', (request, socket, head) => {
var _a;
if (request.headers['upgrade'] !== 'websocket') {
socket.end('HTTP/1.1 400 Bad Request');
return;
@ -39,7 +38,7 @@ export class RTLWebSocketServer {
const acceptKey = request.headers['sec-websocket-key'];
const hash = this.generateAcceptValue(acceptKey);
const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + hash];
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 protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
if (protocols.includes('json')) {
responseHeaders.push('Sec-WebSocket-Protocol: json');
}
@ -53,12 +52,11 @@ export class RTLWebSocketServer {
this.webSocketServer.emit('connection', websocket, request);
};
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 protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
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);

@ -8,7 +8,7 @@ WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
RUN npm install
RUN npm install --legacy-peer-deps
# ---------------
# Build App
@ -22,7 +22,7 @@ RUN npm run buildfrontend
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --production
RUN npm prune --production --legacy-peer-deps
# ---------------
# Release App

@ -12,7 +12,7 @@ WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
RUN npm install
RUN npm install --legacy-peer-deps
# ---------------
# Build App
@ -26,7 +26,7 @@ RUN npm run buildfrontend
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --production
RUN npm prune --production --legacy-peer-deps
# ---------------
# Release App

@ -11,7 +11,7 @@ WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
RUN npm install
RUN npm install --legacy-peer-deps
# ---------------
# Build App
@ -25,7 +25,7 @@ RUN npm run buildfrontend
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --production
RUN npm prune --production --legacy-peer-deps
# ---------------
# Release App

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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

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

Loading…
Cancel
Save