Release 0.12.3 (#1012)

LND Palemoon UX extension panel bug
Cookie file not generated for BTCPayServer #990
ECL Missing fee calculation on Dashboard #975
CLT channel filter on alias bug fix #982
CLightning to Code Lightning #997
Added Infographics for Channel Rebalance
CLN Base fee zero bug fix #987
ECL Query Route bug fix #1007
LND faster initial load
LND Transactions Lookup #1002
LND Bug fix for Routing Fee Calculation
This commit is contained in:
ShahanaFarooqui 2022-05-01 13:35:20 -04:00 committed by GitHub
parent 386ff5afa6
commit 44412d357e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
489 changed files with 3307 additions and 2869 deletions

View File

@ -22,7 +22,7 @@ If applicable, add screenshots to help explain your problem.
**Your environment** **Your environment**
* Version of `RTL` * Version of `RTL`
* Version of `lnd` * Version of `lnd`/`core lightning`/`eclair`
* Version of `btcd`, `bitcoind`, or other backend * Version of `btcd`, `bitcoind`, or other backend
* Browser & browser version * Browser & browser version
* Operating system (`uname -a` on *Nix) * Operating system (`uname -a` on *Nix)

10
.github/README.md vendored
View File

@ -4,7 +4,7 @@
<a href="https://snyk.io/test/github/Ride-The-Lightning/RTL"><img src="https://snyk.io/test/github/Ride-The-Lightning/RTL/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/Ride-The-Lightning/RTL" style="max-width:100%;"></a> <a href="https://snyk.io/test/github/Ride-The-Lightning/RTL"><img src="https://snyk.io/test/github/Ride-The-Lightning/RTL/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/Ride-The-Lightning/RTL" style="max-width:100%;"></a>
[![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE) [![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE)
**Intro** -- [Application Features](./docs/Application_features.md) -- [Road Map](./docs/Roadmap.md) -- [Application Configurations](./docs/Application_configurations.md) -- [C-Lightning](./docs/C-Lightning-setup.md) -- [Eclair](./docs/Eclair-setup.md) -- [Contribution](./docs/Contributing.md) **Intro** -- [Application Features](./docs/Application_features.md) -- [Road Map](./docs/Roadmap.md) -- [Application Configurations](./docs/Application_configurations.md) -- [Core Lightning](./docs/Core_lightning_setup.md) -- [Eclair](./docs/Eclair_setup.md) -- [Contribution](./docs/Contributing.md)
* [Introduction](#intro) * [Introduction](#intro)
* [Architecture](#arch) * [Architecture](#arch)
@ -17,10 +17,10 @@
### <a name="intro"></a>Introduction ### <a name="intro"></a>Introduction
RTL is a full function, device agnostic, web user interface to help manage lightning node operations. RTL is a full function, device agnostic, web user interface to help manage lightning node operations.
RTL is available on [LND](https://github.com/lightningnetwork/lnd), [C-Lightning](https://github.com/ElementsProject/lightning) and [Eclair](https://github.com/ACINQ/eclair) implementations. RTL is available on [LND](https://github.com/lightningnetwork/lnd), [CoreLightning](https://github.com/ElementsProject/lightning) and [Eclair](https://github.com/ACINQ/eclair) implementations.
* C-Lightning users, refer to [this](./docs/C-Lightning-setup.md) page for install instructions. * Core Lightning users, refer to [this](./docs/Core_lightning_setup.md) page for install instructions.
* Eclair users, refer to [this](./docs/Eclair-setup.md) page for install instructions. * Eclair users, refer to [this](./docs/Eclair_setup.md) page for install instructions.
* LND users, follow the instructions below * LND users, follow the instructions below
Pre-requisite for running RTL is a functioning and synced LND node. If you are a Raspberry Pi or a Linux user, you can follow the famous Stadicus's [guide](https://stadicus.github.io/RaspiBolt/) to setup a Bitcoin + Lighting node. Pre-requisite for running RTL is a functioning and synced LND node. If you are a Raspberry Pi or a Linux user, you can follow the famous Stadicus's [guide](https://stadicus.github.io/RaspiBolt/) to setup a Bitcoin + Lighting node.
@ -67,7 +67,7 @@ $ npm install --only=prod
### <a name="prep"></a>Prep for Execution ### <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. RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
*Advanced users can refer to [this page](./docs/Multi-Node-setup.md), for config settings required to manage multiple nodes* *Advanced users can refer to [this page](./docs/Multi_node_setup.md), for config settings required to manage multiple nodes*
* Copy the file `Sample-RTL-Config.json` from `./RTL/docs` to `./RTL` and rename it to `RTL-Config.json`. * Copy the file `Sample-RTL-Config.json` from `./RTL/docs` to `./RTL` and rename it to `RTL-Config.json`.
* Locate the complete path of the readable macroon file (admin.macroon) on your node and the lnd.conf file. * Locate the complete path of the readable macroon file (admin.macroon) on your node and the lnd.conf file.

View File

@ -18,12 +18,12 @@ parameters have `default` values for initial setup and can be updated after RTL
{ {
"index": <Incremental node indices starting from 1, Required>, "index": <Incremental node indices starting from 1, Required>,
"lnNode": "<Node name to uniquely identify the node in the UI, Default 'Node 1', Required>", "lnNode": "<Node name to uniquely identify the node in the UI, Default 'Node 1', Required>",
"lnImplementation": "<LNP implementation, Allowed values LND/CLT/ECL. Default 'LND', Required>", "lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL. Default 'LND', Required>",
"Authentication": { "Authentication": {
"macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLT) file, Required for LND & CLT>", "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>", "swapMacaroonPath": "<Path for the folder containing 'loop.macaroon' (LND), Required for LND Loop>",
"boltzMacaroonPath": "<Path for the folder containing 'admin.macaroon' (Boltz), Required for Boltz Swaps>", "boltzMacaroonPath": "<Path for the folder containing 'admin.macaroon' (Boltz), Required for Boltz Swaps>",
"configPath": "<Full path of the lnd.conf/c-lightning config/eclair.conf file including the file name, if present locally, Optional, only mandatory for ECL if the lnApiPassword is missing>", "configPath": "<Full path of the lnd.conf/core lightning config/eclair.conf file including the file name, if present locally, Optional, only mandatory for ECL if the lnApiPassword is missing>",
"lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>" "lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>"
}, },
"Settings": { "Settings": {
@ -35,7 +35,7 @@ parameters have `default` values for initial setup and can be updated after RTL
"logLevel": <logging levels, will log in accordance with the logLevel value provided, Allowed values ERROR, WARN, INFO, DEBUG>, "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>, "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>", "currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
"lnServerUrl": "<Service url for LND/CLightning 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", "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>", "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>" "boltzServerUrl": "<Service url for boltz server REST APIs for the node, e.g. https://localhost:9003, Optional>"
} }
@ -51,12 +51,12 @@ If the environment variables are set, it will take precedence over the parameter
PORT (port number for the rtl node server, default 3000, Required)<br /> PORT (port number for the rtl node server, default 3000, Required)<br />
HOST (host for the rtl node server, default localhost, Optional)<br /> HOST (host for the rtl node server, default localhost, Optional)<br />
APP_PASSWORD (Plaintext password to be provided by the parent container, NOT suggested for standalone RTL applications, to be used by Umbrel) (Optional)<br /> 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/CLT/ECL. Default 'LND', Required)<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 /> 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 /> 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 /> BOLTZ_SERVER_URL (Boltz server URL for REST APIs, default http://localhost:9003) (Optional)<br />
CONFIG_PATH (Full path of the LNP .conf file including the file name) (Optional for LND & CLT, Mandatory for ECL if LN_API_PASSWORD is undefined)<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' (CLT) file, Required for LND & CLT)<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 /> SWAP_MACAROON_PATH (Path for the folder containing Loop's 'loop.macaroon', optional)<br />
BOLTZ_MACAROON_PATH (Path for the folder containing Boltz's 'admin.macaroon', optional)<br /> BOLTZ_MACAROON_PATH (Path for the folder containing Boltz's 'admin.macaroon', optional)<br />
RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication, Required)<br /> RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication, Required)<br />
@ -65,5 +65,5 @@ LOGOUT_REDIRECT_LINK (URL to re-direct to after logout/timeout from RTL, Require
RTL_CONFIG_PATH (Path for the folder containing 'RTL-Config.json' file, Required)<br /> RTL_CONFIG_PATH (Path for the folder containing 'RTL-Config.json' file, Required)<br />
BITCOIND_CONFIG_PATH (Full path of the bitcoind.conf file including the file name, Optional)<br /> BITCOIND_CONFIG_PATH (Full path of the bitcoind.conf file including the file name, Optional)<br />
CHANNEL_BACKUP_PATH (Folder location for saving the channel backup files, valid for LND implementation only, Required if ln implementation=LND else Optional)<br /> CHANNEL_BACKUP_PATH (Folder location for saving the channel backup files, valid for LND implementation only, Required if ln implementation=LND else Optional)<br />
ENABLE_OFFERS (Boolean flag to enable the offers feature on Clighning, default false, optional)<br /> ENABLE_OFFERS (Boolean flag to enable the offers feature on core lighning, default false, optional)<br />
LN_API_PASSWORD (Password for Eclair implementation if the eclair.conf path is not available, Required if ln implementation=ECL && config path is undefined)<br /> LN_API_PASSWORD (Password for Eclair implementation if the eclair.conf path is not available, Required if ln implementation=ECL && config path is undefined)<br />

View File

@ -1,4 +1,4 @@
### C-Lightning Commands Covered on RTL ### Core Lightning Commands Covered on RTL
=== bitcoin === === bitcoin ===
- [x] feerates - [x] feerates

View File

@ -12,7 +12,7 @@ There are multiple ways you can contribute towards the development and not all o
#### <a name="bug"></a>Bug Report #### <a name="bug"></a>Bug Report
Bug reports are reports of technical or functional issues with the software. Bug reports help with the removal of defects from the software and improve its quality. Guidelines for submitting a bug report: Bug reports are reports of technical or functional issues with the software. Bug reports help with the removal of defects from the software and improve its quality. Guidelines for submitting a bug report:
* Label the bug with the correct Lightning implementation (LND/C-Lightning/Eclair). * Label the bug with the correct Lightning implementation (LND/Core Lightning/Eclair).
* Add the `Bug` label to the issue * Add the `Bug` label to the issue
* Provide details of your configuration like Device, Operating system, Bitcoin version, Lightning implementation version, RTL version etc. * Provide details of your configuration like Device, Operating system, Bitcoin version, Lightning implementation version, RTL version etc.
* Attempt to explain the scenario in detail, so that the developer can try to replicate the issue at their end. * Attempt to explain the scenario in detail, so that the developer can try to replicate the issue at their end.
@ -22,7 +22,7 @@ Bug reports are reports of technical or functional issues with the software. Bug
#### <a name="feature"></a>Feature Request #### <a name="feature"></a>Feature Request
Feature Requests are requests raised to add new features to the application. The features requests can range from technical to functional, making the application better for everyone. Guidelines to follow for create a feature request: Feature Requests are requests raised to add new features to the application. The features requests can range from technical to functional, making the application better for everyone. Guidelines to follow for create a feature request:
* Label the feature request with the correct Lightning implementation (LND/C-Lightning/Eclair). * Label the feature request with the correct Lightning implementation (LND/Core Lightning/Eclair).
* Add the `Enhancement Request` label to the issue * Add the `Enhancement Request` label to the issue
* If the feature relates to an existing aspect of the application, indicate clearly which part of the application the feature request relates to. E.g. Transactions page under Lightning menu. * If the feature relates to an existing aspect of the application, indicate clearly which part of the application the feature request relates to. E.g. Transactions page under Lightning menu.
* Provide the justification for the feature request. E.g. Privacy/Security/Usability benefit. * Provide the justification for the feature request. E.g. Privacy/Security/Usability benefit.
@ -51,13 +51,13 @@ Contributions via code is the most sought after contribution and something we en
* To run RTL node server in development mode, open another command window, go to workspace/RTL and excute `npm run server`. This will run the script named `server` defined in package.json. This script sets the node environment as development and starts the server from rtl.js. Nodemon restarts the node application when file changes in the directory are detected. * To run RTL node server in development mode, open another command window, go to workspace/RTL and excute `npm run server`. This will run the script named `server` defined in package.json. This script sets the node environment as development and starts the server from rtl.js. Nodemon restarts the node application when file changes in the directory are detected.
* This `server` script has been written for windows machine. Please update the script to set the `NODE_ENV=development` according to your machine's OS. * This `server` script has been written for windows machine. Please update the script to set the `NODE_ENV=development` according to your machine's OS.
* To check all available scripts for the project, explore the `scripts` section of package.json. * To check all available scripts for the project, explore the `scripts` section of package.json.
![](./screenshots/node-server-dev.jpg) ![](../screenshots/node-server-dev.jpg)
##### Angular Frontend Server for Development ##### Angular Frontend Server for Development
* The last step starts the node server but it cannot detect and update the code written in Angular. We run the angular development server separately while working on the frontend of the project and package the final build once the development is finished. * The last step starts the node server but it cannot detect and update the code written in Angular. We run the angular development server separately while working on the frontend of the project and package the final build once the development is finished.
* To run the angular development server, go to workspace/RTL and run `npm run start`. It will start the angular server at default '4200' port and serve the application on localhost:4200. * To run the angular development server, go to workspace/RTL and run `npm run start`. It will start the angular server at default '4200' port and serve the application on localhost:4200.
![](./screenshots/angular-server-dev.jpg) ![](../screenshots/angular-server-dev.jpg)
![](./screenshots/localhost-ui-dev.jpg) ![](../screenshots/localhost-ui-dev.jpg)
##### Package Angular Build ##### Package Angular Build
* Run `npm run test` script to verify and fix, if needed, automated test cases. * Run `npm run test` script to verify and fix, if needed, automated test cases.
@ -65,7 +65,7 @@ Contributions via code is the most sought after contribution and something we en
* To compile the backend code, `npm run buildbackend` script should be used. It will compile the code written in typescript in `server` folder and create a folder named `backend` with final compiled javascript code. * To compile the backend code, `npm run buildbackend` script should be used. It will compile the code written in typescript in `server` folder and create a folder named `backend` with final compiled javascript code.
* The Angular application code needs to be compiled into the output directory named `frontend` at workspace/RTL. It can be done by running `npm run buildfrontend` command in the RTL root. * The Angular application code needs to be compiled into the output directory named `frontend` at workspace/RTL. It can be done by running `npm run buildfrontend` command in the RTL root.
* Please make sure to remove all linting and other errors thrown by the build command before moving to the next step. * Please make sure to remove all linting and other errors thrown by the build command before moving to the next step.
![](./screenshots/angular-build.jpg) ![](../screenshots/angular-build.jpg)
##### Create a Pull Request ##### Create a Pull Request
* Create a new branch on the github to push your updated code. * Create a new branch on the github to push your updated code.

View File

@ -1,6 +1,6 @@
![](./screenshots/RTL-CLT-Dashboard.png) ![](../screenshots/RTL-CLN-Dashboard.png)
## RTL C-lightning setup ## RTL Core lightning setup
* [Introduction](#intro) * [Introduction](#intro)
* [Pre-requisite](#prereq) * [Pre-requisite](#prereq)
@ -10,18 +10,18 @@
* [Start the server and access the app](#start) * [Start the server and access the app](#start)
### <a name="intro"></a>Introduction ### <a name="intro"></a>Introduction
RTL is now enabled to manage lightning nodes running C-Lightning. RTL is now enabled to manage lightning nodes running Core Lightning.
Follow the below steps to install and setup RTL to run on C-Lightning. Follow the below steps to install and setup RTL to run on Core Lightning.
### <a name="prereq"></a>Pre-requisites: ### <a name="prereq"></a>Pre-requisites:
1. Functioning C-Lightning node. Follow install instructions on their [github](https://github.com/ElementsProject/lightning) 1. Functioning Core Lightning node. Follow install instructions on their [github](https://github.com/ElementsProject/lightning)
2. NodeJS - Can be downloaded [here](https://nodejs.org/en/download) 2. NodeJS - Can be downloaded [here](https://nodejs.org/en/download)
3. Cl-REST - Ensure that `cl-rest` API server is installed and running. Install instructions [here](https://github.com/Ride-The-Lightning/c-lightning-REST) 3. Cl-REST - Ensure that `cl-rest` API server is installed and running. Install instructions [here](https://github.com/Ride-The-Lightning/c-lightning-REST)
4. Copy the `access.macaroon` file from `cl-rest` to the device, on which RTL will be installed 4. Copy the `access.macaroon` file from `cl-rest` to the device, on which RTL will be installed
### <a name="arch"></a>Architecture ### <a name="arch"></a>Architecture
![](./screenshots/RTL-CLT-Arch-2.png) ![](../screenshots/RTL-CLN-Arch-2.png)
### <a name="install"></a>Installation: ### <a name="install"></a>Installation:
To download a specific RTL version follow the instructions on the [release page](https://github.com/Ride-The-Lightning/RTL/releases) To download a specific RTL version follow the instructions on the [release page](https://github.com/Ride-The-Lightning/RTL/releases)
@ -50,11 +50,11 @@ RTL requires its own config file `RTL-Config.json`, to start the server and prov
* Modify the RTL conf file per the example file below * Modify the RTL conf file per the example file below
Ensure that the follow values are correct per your config: Ensure that the follow values are correct per your config:
* `lnImplementation` - This should be `CLT`, indicating that RTL is connecting to a c-lightning node. * `lnImplementation` - This should be `CLN`, indicating that RTL is connecting to a core lightning node.
* `macaroonPath` - Path of the folder containing `access.macaroon` file from cl-rest server. * `macaroonPath` - Path of the folder containing `access.macaroon` file from cl-rest server.
* `lnServerUrl` - complete url with ip address and port of the cl-rest server. * `lnServerUrl` - complete url with ip address and port of the cl-rest server.
* `multiPass` - Specify the password (in plain text) to access RTL. This password will be hashed and not stored as plain text. * `multiPass` - Specify the password (in plain text) to access RTL. This password will be hashed and not stored as plain text.
* `configPath` (optional) - File path of the c-lightning config file, if RTL server is local to the c-lightning server. * `configPath` (optional) - File path of the core lightning config file, if RTL server is local to the core lightning server.
``` ```
{ {
@ -67,11 +67,11 @@ Ensure that the follow values are correct per your config:
"nodes": [ "nodes": [
{ {
"index": 1, "index": 1,
"lnNode": "c-lightning Testnet # 1", "lnNode": "Core Lightning Testnet # 1",
"lnImplementation": "CLT", "lnImplementation": "CLN",
"Authentication": { "Authentication": {
"macaroonPath": "<Modify to include the path of the folder with access.macaroon>", "macaroonPath": "<Modify to include the path of the folder with access.macaroon>",
"configPath": "<Optional - Config file path for c-lightning>" "configPath": "<Optional - Config file path for core lightning>"
}, },
"Settings": { "Settings": {
"userPersona": "OPERATOR", "userPersona": "OPERATOR",

View File

@ -1,4 +1,4 @@
![](./screenshots/RTL-ECL-Dashboard.png) ![](../screenshots/RTL-ECL-Dashboard.png)
## RTL Eclair setup ## RTL Eclair setup

View File

@ -25,7 +25,7 @@ This step is only required to configure the nodes, which will be remotely connec
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. 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. 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. 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/C Lightining REST APIs for each node, with the unique ip address of the node hosting lnd/clightning 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/clightning is '192.168.0.1' 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. 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. 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. 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.

View File

@ -16,7 +16,7 @@ As the functional complexity increases, we need to add automated testing to ensu
Active node monitoring may be required to ensure reliability of routing nodes. Monitoring can include generating alerts for out-of-balance channels, inactive channels, disconnected peers, low activity channels etc. This feature will be required for professional node operaters running commercial routing nodes with a need to react to signals, requiring specific action to be taken. Active node monitoring may be required to ensure reliability of routing nodes. Monitoring can include generating alerts for out-of-balance channels, inactive channels, disconnected peers, low activity channels etc. This feature will be required for professional node operaters running commercial routing nodes with a need to react to signals, requiring specific action to be taken.
### Advanced Multi-node Management ### Advanced Multi-node Management
RTL currently allows managing multiple nodes (LND or C-Lightning), via single UI. More sophistication can be built on multi-node management, with advanced top level dashboards, which summarize node level summary in a single dashboard. This feature may be required for professional node operators, who are running commercial routing nodes. RTL currently allows managing multiple nodes (LND or Core Lightning or Eclair), via single UI. More sophistication can be built on multi-node management, with advanced top level dashboards, which summarize node level summary in a single dashboard. This feature may be required for professional node operators, who are running commercial routing nodes.
### RTL installer ### RTL installer
Automate RTL setup so that installation process is simpler than the current method of following the steps provided in the Readme file. This should also help with configuration of nginx and letsencrypt, to enable access via https. Contribution on this is more than welcome. Automate RTL setup so that installation process is simpler than the current method of following the steps provided in the Readme file. This should also help with configuration of nginx and letsencrypt, to enable access via https. Contribution on this is more than welcome.

BIN
.github/screenshots/RTL-CLN-Arch-2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

View File

@ -9,7 +9,7 @@ const common = Common;
const clWsClient = CLWSClient; const clWsClient = CLWSClient;
const databaseService = Database; const databaseService = Database;
export const getInfo = (req, res, next) => { export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting CLightning Node Information..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Core Lightning Node Information..' });
common.logEnvVariables(req); common.logEnvVariables(req);
common.setOptions(req); common.setOptions(req);
options = common.getOptions(req); options = common.getOptions(req);
@ -18,9 +18,9 @@ export const getInfo = (req, res, next) => {
} }
options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo'; options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.ln_node }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.ln_node });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Calling Info from C-Lightning server url ' + options.url }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Calling Info from Core Lightning server url ' + options.url });
if (!options.headers || !options.headers.macaroon) { if (!options.headers || !options.headers.macaroon) {
const errMsg = 'C-Lightning get info failed due to bad or missing macaroon!'; const errMsg = 'Core lightning get info failed due to bad or missing macaroon!';
const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode); const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
} }
@ -36,7 +36,7 @@ export const getInfo = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
} }
else { else {
body.lnImplementation = 'C-Lightning'; body.lnImplementation = 'Core Lightning';
const chainObj = { chain: '', network: '' }; const chainObj = { chain: '', network: '' };
if (body.network === 'testnet') { if (body.network === 'testnet') {
chainObj.chain = 'Bitcoin'; chainObj.chain = 'Bitcoin';
@ -67,7 +67,7 @@ export const getInfo = (req, res, next) => {
} }
req.session.selectedNode.api_version = body.api_version || ''; req.session.selectedNode.api_version = body.api_version || '';
req.session.selectedNode.ln_version = body.version || ''; req.session.selectedNode.ln_version = body.version || '';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the C-Lightning\'s Websocket Server.' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' });
clWsClient.updateSelectedNode(req.session.selectedNode); clWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode); databaseService.loadDatabase(req.session.selectedNode);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });

View File

@ -19,7 +19,7 @@ export class CLWebSocketClient {
this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2); this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2);
this.reconnectTimeOut = setTimeout(() => { this.reconnectTimeOut = setTimeout(() => {
if (clWsClt.selectedNode) { if (clWsClt.selectedNode) {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Reconnecting to the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Reconnecting to the Core Lightning\'s Websocket Server..' });
this.connect(clWsClt.selectedNode); this.connect(clWsClt.selectedNode);
} }
this.reconnectTimeOut = null; this.reconnectTimeOut = null;
@ -47,16 +47,16 @@ export class CLWebSocketClient {
} }
}; };
this.connectWithClient = (clWsClt) => { this.connectWithClient = (clWsClt) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' });
const WS_LINK = (clWsClt.selectedNode.ln_server_url).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'); 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 = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false });
clWsClt.webSocketClient.onopen = () => { clWsClt.webSocketClient.onopen = () => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' });
this.waitTime = 0.5; this.waitTime = 0.5;
}; };
clWsClt.webSocketClient.onclose = (e) => { clWsClt.webSocketClient.onclose = (e) => {
if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLT') { if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLN') {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...' });
clWsClt.webSocketClient.close(); clWsClt.webSocketClient.close();
if (clWsClt.reConnect) { if (clWsClt.reConnect) {
@ -67,7 +67,7 @@ export class CLWebSocketClient {
clWsClt.webSocketClient.onmessage = (msg) => { clWsClt.webSocketClient.onmessage = (msg) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data });
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data; msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
msg['source'] = 'CLT'; msg['source'] = 'CLN';
const msgStr = JSON.stringify(msg); const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode); this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
}; };
@ -89,7 +89,7 @@ export class CLWebSocketClient {
this.disconnect = (selectedNode) => { this.disconnect = (selectedNode) => {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index); const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.readyState === WebSocket.OPEN) { if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.readyState === WebSocket.OPEN) {
this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the Core Lightning\'s Websocket Server..' });
clientExists.reConnect = false; clientExists.reConnect = false;
clientExists.webSocketClient.close(); clientExists.webSocketClient.close();
const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index); const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index);
@ -105,10 +105,10 @@ export class CLWebSocketClient {
newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode)); newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
this.webSocketClients[clientIdx] = newClient; this.webSocketClients[clientIdx] = newClient;
}; };
this.wsServer.eventEmitterCLT.on('CONNECT', (nodeIndex) => { this.wsServer.eventEmitterCLN.on('CONNECT', (nodeIndex) => {
this.connect(this.common.findNode(+nodeIndex)); this.connect(this.common.findNode(+nodeIndex));
}); });
this.wsServer.eventEmitterCLT.on('DISCONNECT', (nodeIndex) => { this.wsServer.eventEmitterCLN.on('DISCONNECT', (nodeIndex) => {
this.disconnect(this.common.findNode(+nodeIndex)); this.disconnect(this.common.findNode(+nodeIndex));
}); });
} }

View File

@ -11,6 +11,18 @@ export const arrangeFees = (selNode, body, current_time) => {
let fee = 0; let fee = 0;
body.relayed.forEach((relayedEle) => { body.relayed.forEach((relayedEle) => {
fee = Math.round((relayedEle.amountIn - relayedEle.amountOut) / 1000); fee = Math.round((relayedEle.amountIn - relayedEle.amountOut) / 1000);
if (relayedEle.timestamp) {
if (relayedEle.timestamp.unix) {
if ((relayedEle.timestamp.unix * 1000) >= day_start_time) {
fees.daily_fee = fees.daily_fee + fee;
fees.daily_txs = fees.daily_txs + 1;
}
if ((relayedEle.timestamp.unix * 1000) >= week_start_time) {
fees.weekly_fee = fees.weekly_fee + fee;
fees.weekly_txs = fees.weekly_txs + 1;
}
}
else {
if (relayedEle.timestamp >= day_start_time) { if (relayedEle.timestamp >= day_start_time) {
fees.daily_fee = fees.daily_fee + fee; fees.daily_fee = fees.daily_fee + fee;
fees.daily_txs = fees.daily_txs + 1; fees.daily_txs = fees.daily_txs + 1;
@ -19,6 +31,8 @@ export const arrangeFees = (selNode, body, current_time) => {
fees.weekly_fee = fees.weekly_fee + fee; fees.weekly_fee = fees.weekly_fee + fee;
fees.weekly_txs = fees.weekly_txs + 1; fees.weekly_txs = fees.weekly_txs + 1;
} }
}
}
fees.monthly_fee = fees.monthly_fee + fee; fees.monthly_fee = fees.monthly_fee + fee;
fees.monthly_txs = fees.monthly_txs + 1; fees.monthly_txs = fees.monthly_txs + 1;
}); });

View File

@ -22,7 +22,7 @@ export const getSentInfoFromPaymentRequest = (selNode, payment) => {
}; };
export const getQueryNodes = (selNode, nodeIds) => { export const getQueryNodes = (selNode, nodeIds) => {
options.url = selNode.ln_server_url + '/nodes'; options.url = selNode.ln_server_url + '/nodes';
options.form = { nodeIds: nodeIds }; options.form = { nodeIds: nodeIds.reduce((acc, curr) => acc + ',' + curr) };
return request.post(options).then((nodes) => { return request.post(options).then((nodes) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes }); logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes });
return nodes; return nodes;
@ -78,22 +78,25 @@ export const queryPaymentRoute = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form });
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body });
if (body && body.length) { if (body && body.routes && body.routes.length) {
const queryRoutes = []; let allRoutesNodeIds = [];
return getQueryNodes(req.session.selectedNode, body).then((hopsWithAlias) => { allRoutesNodeIds = body.routes.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
return getQueryNodes(req.session.selectedNode, allRoutesNodeIds).then((nodesWithAlias) => {
let foundPeer = null; let foundPeer = null;
body.map((hop) => { body.routes.forEach((route, i) => {
foundPeer = hopsWithAlias.find((hopWithAlias) => hop === hopWithAlias.nodeId); route.nodeIds.map((node, j) => {
queryRoutes.push({ nodeId: hop, alias: foundPeer ? foundPeer.alias : '' }); foundPeer = nodesWithAlias.find((nodeWithAlias) => node === nodeWithAlias.nodeId);
return hop; body.routes[i].nodeIds[j] = { nodeId: node, alias: foundPeer ? foundPeer.alias : '' };
return node;
}); });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Query Routes with Alias Received', data: queryRoutes }); });
res.status(200).json(queryRoutes); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Query Routes with Alias Received', data: body });
res.status(200).json(body);
}); });
} }
else { else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Empty Payment Route Information Received' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Empty Payment Route Information Received' });
res.status(200).json([]); res.status(200).json({ routes: [] });
} }
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'Query Route Error', req.session.selectedNode); const err = common.handleError(errRes, 'Payments', 'Query Route Error', req.session.selectedNode);

View File

@ -20,7 +20,7 @@ export const getFees = (req, res, next) => {
const month_start_time = (Math.round(start_date.getTime() / 1000)); const month_start_time = (Math.round(start_date.getTime() / 1000));
const week_start_time = current_time - 604800; const week_start_time = current_time - 604800;
const day_start_time = current_time - 86400; const day_start_time = current_time - 86400;
return getAllForwardingEvents(req, month_start_time, current_time, 0, (history) => { return getAllForwardingEvents(req, month_start_time, current_time, 0, 'fees', (history) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Forwarding History Received', data: history }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Forwarding History Received', data: history });
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 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 weekly_sum = history.forwarding_events.reduce((acc, curr) => ((curr.timestamp >= week_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);

View File

@ -6,13 +6,19 @@ let options = null;
const logger = Logger; const logger = Logger;
const common = Common; const common = Common;
const lndWsClient = LNDWSClient; const lndWsClient = LNDWSClient;
export const getInvoice = (req, res, next) => { export const invoiceLookup = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Getting Invoice Information..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Getting Invoice Information..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error }); return res.status(options.statusCode).json({ message: options.message, error: options.error });
} }
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/' + req.params.rHashStr; options.url = req.session.selectedNode.ln_server_url + '/v2/invoices/lookup';
if (req.query.payment_addr) {
options.url = options.url + '?payment_addr=' + req.query.payment_addr;
}
else {
options.url = options.url + '?payment_hash=' + req.query.payment_hash;
}
request(options).then((body) => { request(options).then((body) => {
body.r_preimage = body.r_preimage ? Buffer.from(body.r_preimage, 'base64').toString('hex') : ''; body.r_preimage = body.r_preimage ? Buffer.from(body.r_preimage, 'base64').toString('hex') : '';
body.r_hash = body.r_hash ? Buffer.from(body.r_hash, 'base64').toString('hex') : ''; body.r_hash = body.r_hash ? Buffer.from(body.r_hash, 'base64').toString('hex') : '';
@ -20,7 +26,7 @@ export const getInvoice = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Information Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Information Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Invoices', 'Get Invoice Error', req.session.selectedNode); const err = common.handleError(errRes, 'Invoices', 'Invoice Lookup Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };

View File

@ -72,15 +72,31 @@ export const getAllLightningTransactions = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Getting All Lightning Transactions..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Getting All Lightning Transactions..' });
const options1 = JSON.parse(JSON.stringify(common.getOptions(req))); const options1 = JSON.parse(JSON.stringify(common.getOptions(req)));
const options2 = JSON.parse(JSON.stringify(common.getOptions(req))); const options2 = JSON.parse(JSON.stringify(common.getOptions(req)));
options1.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=100000&index_offset=0&reversed=true'; // options1.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=100000&index_offset=0&reversed=true';
options2.url = req.session.selectedNode.ln_server_url + '/v1/invoices?num_max_invoices=100000&index_offset=0&reversed=true'; options2.url = req.session.selectedNode.ln_server_url + '/v1/invoices?num_max_invoices=100000&index_offset=0&reversed=true';
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Payments Options', data: options1 }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Payments Options', data: options1 });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Invoices Options', data: options2 }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Invoices Options', data: options2 });
return Promise.all([request(options1), request(options2)]).then((values) => { // return Promise.all([request(options1), request(options2)]).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'All Lightning Transactions Received', data: ({ totalPayments: values[0].length || 0, totalInvoices: values[1].length || 0 }) }); return Promise.all([{ payments: [] }, request(options2)]).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'All Lightning Transactions Received', data: ({ totalPayments: values[0].payments.length || 0, totalInvoices: values[1].invoices.length || 0 }) });
res.status(200).json({ listPaymentsAll: values[0], listInvoicesAll: values[1] }); res.status(200).json({ listPaymentsAll: values[0], listInvoicesAll: values[1] });
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'All Lightning Transactions Error', req.session.selectedNode); const err = common.handleError(errRes, 'Payments', 'All Lightning Transactions Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const paymentLookup = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Looking up Payment..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/router/track/' + req.params.paymentHash;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Information Received for ' + req.params.paymentHash, data: body });
res.status(200).json(body.result || body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'Payment Lookup Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

View File

@ -4,10 +4,10 @@ import { Common } from '../../utils/common.js';
let options = null; let options = null;
const logger = Logger; const logger = Logger;
const common = Common; const common = Common;
let responseData = { forwarding_events: [], last_offset_index: 0 }; const responseData = { switch: { forwarding_events: [], last_offset_index: 0 }, fees: { forwarding_events: [], last_offset_index: 0 } };
const num_max_events = 100; const num_max_events = 100;
export const forwardingHistory = (req, res, next) => { export const forwardingHistory = (req, res, next) => {
getAllForwardingEvents(req, req.body.start_time, req.body.end_time, 0, (eventsResponse) => { getAllForwardingEvents(req, req.body.start_time, req.body.end_time, 0, 'switch', (eventsResponse) => {
if (eventsResponse.error) { if (eventsResponse.error) {
res.status(eventsResponse.error.statusCode).json(eventsResponse); res.status(eventsResponse.error.statusCode).json(eventsResponse);
} }
@ -16,10 +16,10 @@ export const forwardingHistory = (req, res, next) => {
} }
}); });
}; };
export const getAllForwardingEvents = (req, start, end, offset, callback) => { export const getAllForwardingEvents = (req, start, end, offset, caller, callback) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Getting Forwarding Events..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Getting Forwarding Events..' });
if (offset === 0) { if (offset === 0) {
responseData = { forwarding_events: [], last_offset_index: 0 }; responseData[caller] = { forwarding_events: [], last_offset_index: 0 };
} }
if (!req.session.selectedNode) { if (!req.session.selectedNode) {
const err = common.handleError({ message: 'Session Expired after a day\'s inactivity.', statusCode: 401 }, 'Balance', 'Get Balance Error', req.session.selectedNode); const err = common.handleError({ message: 'Session Expired after a day\'s inactivity.', statusCode: 401 }, 'Balance', 'Get Balance Error', req.session.selectedNode);
@ -41,17 +41,18 @@ export const getAllForwardingEvents = (req, start, end, offset, callback) => {
return request.post(options).then((body) => { return request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Forwarding Events Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Forwarding Events Received', data: body });
if (body.forwarding_events) { if (body.forwarding_events) {
responseData.forwarding_events.push(...body.forwarding_events); responseData[caller].forwarding_events.push(...body.forwarding_events);
responseData[caller].last_offset_index = body.last_offset_index ? body.last_offset_index : 0;
} }
if (!body.last_offset_index || body.last_offset_index < offset + num_max_events) { if (!body.last_offset_index || body.last_offset_index < offset + num_max_events) {
responseData.last_offset_index = body.last_offset_index ? body.last_offset_index : 0; responseData[caller].last_offset_index = body.last_offset_index ? body.last_offset_index : 0;
if (responseData.forwarding_events) { if (responseData[caller].forwarding_events) {
responseData.forwarding_events = common.sortDescByKey(responseData.forwarding_events, 'timestamp'); responseData[caller].forwarding_events = common.sortDescByKey(responseData[caller].forwarding_events, 'timestamp');
} }
return callback(responseData); return callback(responseData[caller]);
} }
else { else {
return getAllForwardingEvents(req, start, end, offset + num_max_events, callback); return getAllForwardingEvents(req, start, end, offset + num_max_events, caller, callback);
} }
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Switch', 'Get All Forwarding Events Error', req.session.selectedNode); const err = common.handleError(errRes, 'Switch', 'Get All Forwarding Events Error', req.session.selectedNode);

View File

@ -65,6 +65,22 @@ export class LNDWebSocketClient {
this.wsServer.sendErrorToAllLNClients(errStr, selectedNode); this.wsServer.sendErrorToAllLNClients(errStr, selectedNode);
}); });
}; };
this.subscribeToPayment = (options, selectedNode, paymentHash) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Payment ' + paymentHash + ' ..' });
options.url = selectedNode.ln_server_url + '/v2/router/track/' + paymentHash;
request(options).then((msg) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Information Received for ' + paymentHash });
msg['type'] = 'payment';
msg['source'] = 'LND';
const msgStr = JSON.stringify(msg);
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Info Received', data: msgStr });
this.wsServer.sendEventsToAllLNClients(msgStr, selectedNode);
}).catch((errRes) => {
const err = this.common.handleError(errRes, 'Payment', 'Subscribe to Payment Error for ' + paymentHash, selectedNode);
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message + ' ' + paymentHash }) : (typeof err === 'object') ? JSON.stringify({ error: err + ' ' + paymentHash }) : ('{ "error": ' + err + ' ' + paymentHash + ' }'));
this.wsServer.sendErrorToAllLNClients(errStr, selectedNode);
});
};
this.setOptionsForSelNode = (selectedNode) => { this.setOptionsForSelNode = (selectedNode) => {
const options = { url: '', rejectUnauthorized: false, json: true, form: null }; const options = { url: '', rejectUnauthorized: false, json: true, form: null };
try { try {

View File

@ -53,8 +53,7 @@ export const authenticateUser = (req, res, next) => {
res.status(406).json({ message: 'SSO Authentication Error', error: 'Login with Password is not allowed with SSO.' }); res.status(406).json({ message: 'SSO Authentication Error', error: 'Login with Password is not allowed with SSO.' });
} }
else if (req.body.authenticateWith === 'PASSWORD') { else if (req.body.authenticateWith === 'PASSWORD') {
const cookieValue = common.readCookie(); if (common.cookie_value.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(common.cookie_value).digest('hex'), 'utf-8'), Buffer.from(req.body.authenticationValue, 'utf-8'))) {
if (cookieValue.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(cookieValue).digest('hex'), 'utf-8'), Buffer.from(req.body.authenticationValue, 'utf-8'))) {
common.refreshCookie(); common.refreshCookie();
if (!req.session.selectedNode) { if (!req.session.selectedNode) {
req.session.selectedNode = common.initSelectedNode; req.session.selectedNode = common.initSelectedNode;

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getBalance } from '../../controllers/c-lightning/balance.js'; import { getBalance } from '../../controllers/cln/balance.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getBalance); router.get('/', isAuthenticated, getBalance);
export default router; export default router;

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards } from '../../controllers/c-lightning/channels.js'; import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards } from '../../controllers/cln/channels.js';
const router = Router(); const router = Router();
router.get('/listChannels', isAuthenticated, listChannels); router.get('/listChannels', isAuthenticated, listChannels);
router.post('/', isAuthenticated, openChannel); router.post('/', isAuthenticated, openChannel);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getFees } from '../../controllers/c-lightning/fees.js'; import { getFees } from '../../controllers/cln/fees.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getFees); router.get('/', isAuthenticated, getFees);
export default router; export default router;

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getInfo } from '../../controllers/c-lightning/getInfo.js'; import { getInfo } from '../../controllers/cln/getInfo.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getInfo); router.get('/', isAuthenticated, getInfo);
export default router; export default router;

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listInvoices, addInvoice, deleteExpiredInvoice } from '../../controllers/c-lightning/invoices.js'; import { listInvoices, addInvoice, deleteExpiredInvoice } from '../../controllers/cln/invoices.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, listInvoices); router.get('/', isAuthenticated, listInvoices);
router.post('/', isAuthenticated, addInvoice); router.post('/', isAuthenticated, addInvoice);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { signMessage, verifyMessage } from '../../controllers/c-lightning/message.js'; import { signMessage, verifyMessage } from '../../controllers/cln/message.js';
const router = Router(); const router = Router();
router.post('/sign', isAuthenticated, signMessage); router.post('/sign', isAuthenticated, signMessage);
router.post('/verify', isAuthenticated, verifyMessage); router.post('/verify', isAuthenticated, verifyMessage);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getRoute, listNode, listChannel, feeRates } from '../../controllers/c-lightning/network.js'; import { getRoute, listNode, listChannel, feeRates } from '../../controllers/cln/network.js';
const router = Router(); const router = Router();
router.get('/getRoute/:destPubkey/:amount', isAuthenticated, getRoute); router.get('/getRoute/:destPubkey/:amount', isAuthenticated, getRoute);
router.get('/listNode/:id', isAuthenticated, listNode); router.get('/listNode/:id', isAuthenticated, listNode);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/c-lightning/offers.js'; import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/cln/offers.js';
const router = Router(); const router = Router();
router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks); router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks);
router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark); router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getNewAddress, onChainWithdraw, getUTXOs } from '../../controllers/c-lightning/onchain.js'; import { getNewAddress, onChainWithdraw, getUTXOs } from '../../controllers/cln/onchain.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getNewAddress); router.get('/', isAuthenticated, getNewAddress);
router.post('/', isAuthenticated, onChainWithdraw); router.post('/', isAuthenticated, onChainWithdraw);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listPayments, decodePayment, postPayment } from '../../controllers/c-lightning/payments.js'; import { listPayments, decodePayment, postPayment } from '../../controllers/cln/payments.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, listPayments); router.get('/', isAuthenticated, listPayments);
router.get('/decode/:payReq', isAuthenticated, decodePayment); router.get('/decode/:payReq', isAuthenticated, decodePayment);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getPeers, postPeer, deletePeer } from '../../controllers/c-lightning/peers.js'; import { getPeers, postPeer, deletePeer } from '../../controllers/cln/peers.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getPeers); router.get('/', isAuthenticated, getPeers);
router.post('/', isAuthenticated, postPeer); router.post('/', isAuthenticated, postPeer);

View File

@ -1,9 +1,9 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listInvoices, getInvoice, addInvoice } from '../../controllers/lnd/invoices.js'; import { listInvoices, invoiceLookup, addInvoice } from '../../controllers/lnd/invoices.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, listInvoices); router.get('/', isAuthenticated, listInvoices);
router.get('/:rHashStr', isAuthenticated, getInvoice); router.get('/lookup/', isAuthenticated, invoiceLookup);
router.post('/', isAuthenticated, addInvoice); router.post('/', isAuthenticated, addInvoice);
export default router; export default router;

View File

@ -1,10 +1,11 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { decodePayment, decodePayments, getPayments, getAllLightningTransactions } from '../../controllers/lnd/payments.js'; import { decodePayment, decodePayments, getPayments, getAllLightningTransactions, paymentLookup } from '../../controllers/lnd/payments.js';
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getPayments); router.get('/', isAuthenticated, getPayments);
router.get('/alltransactions', isAuthenticated, getAllLightningTransactions); router.get('/alltransactions', isAuthenticated, getAllLightningTransactions);
router.get('/decode/:payRequest', isAuthenticated, decodePayment); router.get('/decode/:payRequest', isAuthenticated, decodePayment);
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
router.post('/', isAuthenticated, decodePayments); router.post('/', isAuthenticated, decodePayments);
export default router; export default router;

View File

@ -8,12 +8,12 @@ import CORS from './cors.js';
import CSRF from './csrf.js'; import CSRF from './csrf.js';
import sharedRoutes from '../routes/shared/index.js'; import sharedRoutes from '../routes/shared/index.js';
import lndRoutes from '../routes/lnd/index.js'; import lndRoutes from '../routes/lnd/index.js';
import clRoutes from '../routes/c-lightning/index.js'; import clnRoutes from '../routes/cln/index.js';
import eclRoutes from '../routes/eclair/index.js'; import eclRoutes from '../routes/eclair/index.js';
import { Common } from './common.js'; import { Common } from './common.js';
import { Logger } from './logger.js'; import { Logger } from './logger.js';
import { Config } from './config.js'; import { Config } from './config.js';
import { CLWSClient } from '../controllers/c-lightning/webSocketClient.js'; import { CLWSClient } from '../controllers/cln/webSocketClient.js';
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js'; import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js'; import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
const ONE_DAY = 1000 * 60 * 60 * 24; const ONE_DAY = 1000 * 60 * 60 * 24;
@ -37,7 +37,7 @@ export class ExpressApplication {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' }); this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' });
this.app.use(this.common.baseHref + '/api', sharedRoutes); this.app.use(this.common.baseHref + '/api', sharedRoutes);
this.app.use(this.common.baseHref + '/api/lnd', lndRoutes); this.app.use(this.common.baseHref + '/api/lnd', lndRoutes);
this.app.use(this.common.baseHref + '/api/cl', clRoutes); this.app.use(this.common.baseHref + '/api/cln', clnRoutes);
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes); this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend'))); this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
this.app.use((req, res, next) => { this.app.use((req, res, next) => {

View File

@ -18,6 +18,7 @@ export class CommonService {
this.rtl_sso = 0; this.rtl_sso = 0;
this.rtl_cookie_path = ''; this.rtl_cookie_path = '';
this.logout_redirect_link = ''; this.logout_redirect_link = '';
this.cookie_value = '';
this.api_version = ''; this.api_version = '';
this.secret_key = crypto.randomBytes(64).toString('hex'); this.secret_key = crypto.randomBytes(64).toString('hex');
this.read_dummy_data = false; this.read_dummy_data = false;
@ -82,7 +83,7 @@ export class CommonService {
try { try {
if (req.session.selectedNode && req.session.selectedNode.ln_implementation) { if (req.session.selectedNode && req.session.selectedNode.ln_implementation) {
switch (req.session.selectedNode.ln_implementation.toUpperCase()) { switch (req.session.selectedNode.ln_implementation.toUpperCase()) {
case 'CLT': case 'CLN':
req.session.selectedNode.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon'))).toString('base64') }; req.session.selectedNode.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon'))).toString('base64') };
break; break;
case 'ECL': case 'ECL':
@ -124,7 +125,7 @@ export class CommonService {
try { try {
if (node.ln_implementation) { if (node.ln_implementation) {
switch (node.ln_implementation.toUpperCase()) { switch (node.ln_implementation.toUpperCase()) {
case 'CLT': case 'CLN':
node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') }; node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') };
break; break;
case 'ECL': case 'ECL':
@ -214,7 +215,7 @@ export class CommonService {
delete err.response.request.headers['Grpc-Metadata-macaroon']; delete err.response.request.headers['Grpc-Metadata-macaroon'];
} }
break; break;
case 'CLT': case 'CLN':
if (err.options && err.options.headers && err.options.headers.macaroon) { if (err.options && err.options.headers && err.options.headers.macaroon) {
delete err.options.headers.macaroon; delete err.options.headers.macaroon;
} }
@ -282,7 +283,7 @@ export class CommonService {
const exists = fs.existsSync(this.rtl_cookie_path); const exists = fs.existsSync(this.rtl_cookie_path);
if (exists) { if (exists) {
try { try {
return fs.readFileSync(this.rtl_cookie_path, 'utf-8'); this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
} }
catch (err) { 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: 'Config', msg: 'Something went wrong while reading cookie: \n' + err });
@ -294,7 +295,7 @@ export class CommonService {
const directoryName = dirname(this.rtl_cookie_path); const directoryName = dirname(this.rtl_cookie_path);
this.createDirectory(directoryName); this.createDirectory(directoryName);
fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex')); fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex'));
return fs.readFileSync(this.rtl_cookie_path, 'utf-8'); this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
} }
catch (err) { 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: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err });
@ -305,6 +306,7 @@ export class CommonService {
this.refreshCookie = () => { this.refreshCookie = () => {
try { try {
fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex')); fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex'));
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
} }
catch (err) { catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while refreshing cookie', error: err }); this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while refreshing cookie', error: err });
@ -438,7 +440,7 @@ export class CommonService {
break; break;
} }
} }
else if (lnImplementation === 'CLT') { else if (lnImplementation === 'CLN') {
switch (dataKey) { switch (dataKey) {
case 'GetInfo': case 'GetInfo':
search_string = 'DEBUG: GetInfo => Node Information. '; search_string = 'DEBUG: GetInfo => Node Information. ';

View File

@ -137,6 +137,9 @@ export class ConfigService {
this.common.nodes[idx].index = node.index; this.common.nodes[idx].index = node.index;
this.common.nodes[idx].ln_node = node.lnNode; 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() !== '') { 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; this.common.nodes[idx].macaroon_path = process.env.MACAROON_PATH;
} }
@ -307,9 +310,14 @@ export class ConfigService {
else if (config.SSO && config.SSO.logoutRedirectLink) { else if (config.SSO && config.SSO.logoutRedirectLink) {
this.common.logout_redirect_link = config.SSO.logoutRedirectLink; this.common.logout_redirect_link = config.SSO.logoutRedirectLink;
} }
if (+this.common.rtl_sso && (!this.common.rtl_cookie_path || this.common.rtl_cookie_path.trim() === '')) { if (+this.common.rtl_sso) {
if (!this.common.rtl_cookie_path || this.common.rtl_cookie_path.trim() === '') {
this.errMsg = 'Please set rtlCookiePath value for single sign on option!'; this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
} }
else {
this.common.readCookie();
}
}
}; };
this.setSelectedNode = (config) => { this.setSelectedNode = (config) => {
if (config.defaultNodeIndex) { if (config.defaultNodeIndex) {

View File

@ -11,7 +11,7 @@ export class WebSocketServer {
this.logger = Logger; this.logger = Logger;
this.common = Common; this.common = Common;
this.clientDetails = []; this.clientDetails = [];
this.eventEmitterCLT = new EventEmitter(); this.eventEmitterCLN = new EventEmitter();
this.eventEmitterECL = new EventEmitter(); this.eventEmitterECL = new EventEmitter();
this.eventEmitterLND = new EventEmitter(); this.eventEmitterLND = new EventEmitter();
this.webSocketServer = null; this.webSocketServer = null;
@ -104,8 +104,8 @@ export class WebSocketServer {
case 'LND': case 'LND':
this.eventEmitterLND.emit('DISCONNECT', prevNodeIndex); this.eventEmitterLND.emit('DISCONNECT', prevNodeIndex);
break; break;
case 'CLT': case 'CLN':
this.eventEmitterCLT.emit('DISCONNECT', prevNodeIndex); this.eventEmitterCLN.emit('DISCONNECT', prevNodeIndex);
break; break;
case 'ECL': case 'ECL':
this.eventEmitterECL.emit('DISCONNECT', prevNodeIndex); this.eventEmitterECL.emit('DISCONNECT', prevNodeIndex);
@ -134,8 +134,8 @@ export class WebSocketServer {
case 'LND': case 'LND':
this.eventEmitterLND.emit('CONNECT', currNodeIndex); this.eventEmitterLND.emit('CONNECT', currNodeIndex);
break; break;
case 'CLT': case 'CLN':
this.eventEmitterCLT.emit('CONNECT', currNodeIndex); this.eventEmitterCLN.emit('CONNECT', currNodeIndex);
break; break;
case 'ECL': case 'ECL':
this.eventEmitterECL.emit('CONNECT', currNodeIndex); this.eventEmitterECL.emit('CONNECT', currNodeIndex);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

986
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
{ {
"name": "rtl", "name": "rtl",
"version": "0.12.2-beta", "version": "0.12.3-beta",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --open", "start": "ng serve --open",
"prebuildfrontend": "node src/prebuild.cjs", "prebuildfrontend": "node src/prebuild.cjs",
"buildfrontend": "ng build --configuration production", "buildfrontend": "ng test --watch=false && ng build --configuration production",
"buildbackend": "tsc --project tsconfig.json", "buildbackend": "tsc --project tsconfig.json",
"watchbackend": "tsc --project tsconfig.json --watch", "watchbackend": "tsc --project tsconfig.json --watch",
"server": "set NODE_ENV=development&&nodemon ./rtl.js", "server": "set NODE_ENV=development&&nodemon ./rtl.js",
@ -64,7 +64,7 @@
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^13.0.3", "@angular-devkit/build-angular": "^13.0.3",
"@angular-eslint/builder": "13.0.1", "@angular-eslint/builder": "^12.2.1",
"@angular-eslint/eslint-plugin": "13.0.1", "@angular-eslint/eslint-plugin": "13.0.1",
"@angular-eslint/eslint-plugin-template": "13.0.1", "@angular-eslint/eslint-plugin-template": "13.0.1",
"@angular-eslint/schematics": "13.0.1", "@angular-eslint/schematics": "13.0.1",

View File

@ -11,16 +11,16 @@ const clWsClient: CLWebSocketClient = CLWSClient;
const databaseService: DatabaseService = Database; const databaseService: DatabaseService = Database;
export const getInfo = (req, res, next) => { export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting CLightning Node Information..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Core Lightning Node Information..' });
common.logEnvVariables(req); common.logEnvVariables(req);
common.setOptions(req); common.setOptions(req);
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo'; options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.ln_node }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.ln_node });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Calling Info from C-Lightning server url ' + options.url }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Calling Info from Core Lightning server url ' + options.url });
if (!options.headers || !options.headers.macaroon) { if (!options.headers || !options.headers.macaroon) {
const errMsg = 'C-Lightning get info failed due to bad or missing macaroon!'; const errMsg = 'Core lightning get info failed due to bad or missing macaroon!';
const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode); const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
} else { } else {
@ -32,7 +32,7 @@ export const getInfo = (req, res, next) => {
const err = common.handleError(body, 'GetInfo', 'Get Info Error', req.session.selectedNode); const err = common.handleError(body, 'GetInfo', 'Get Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
} else { } else {
body.lnImplementation = 'C-Lightning'; body.lnImplementation = 'Core Lightning';
const chainObj = { chain: '', network: '' }; const chainObj = { chain: '', network: '' };
if (body.network === 'testnet') { if (body.network === 'testnet') {
chainObj.chain = 'Bitcoin'; chainObj.chain = 'Bitcoin';
@ -59,7 +59,7 @@ export const getInfo = (req, res, next) => {
} }
req.session.selectedNode.api_version = body.api_version || ''; req.session.selectedNode.api_version = body.api_version || '';
req.session.selectedNode.ln_version = body.version || ''; req.session.selectedNode.ln_version = body.version || '';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the C-Lightning\'s Websocket Server.' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' });
clWsClient.updateSelectedNode(req.session.selectedNode); clWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session.selectedNode); databaseService.loadDatabase(req.session.selectedNode);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });

View File

@ -17,10 +17,10 @@ export class CLWebSocketClient {
public waitTime = 0.5; public waitTime = 0.5;
constructor() { constructor() {
this.wsServer.eventEmitterCLT.on('CONNECT', (nodeIndex) => { this.wsServer.eventEmitterCLN.on('CONNECT', (nodeIndex) => {
this.connect(this.common.findNode(+nodeIndex)); this.connect(this.common.findNode(+nodeIndex));
}); });
this.wsServer.eventEmitterCLT.on('DISCONNECT', (nodeIndex) => { this.wsServer.eventEmitterCLN.on('DISCONNECT', (nodeIndex) => {
this.disconnect(this.common.findNode(+nodeIndex)); this.disconnect(this.common.findNode(+nodeIndex));
}); });
} }
@ -30,7 +30,7 @@ export class CLWebSocketClient {
this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2); this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2);
this.reconnectTimeOut = setTimeout(() => { this.reconnectTimeOut = setTimeout(() => {
if (clWsClt.selectedNode) { if (clWsClt.selectedNode) {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Reconnecting to the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Reconnecting to the Core Lightning\'s Websocket Server..' });
this.connect(clWsClt.selectedNode); this.connect(clWsClt.selectedNode);
} }
this.reconnectTimeOut = null; this.reconnectTimeOut = null;
@ -58,18 +58,18 @@ export class CLWebSocketClient {
}; };
public connectWithClient = (clWsClt) => { public connectWithClient = (clWsClt) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' });
const WS_LINK = (clWsClt.selectedNode.ln_server_url).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'); 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 = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false });
clWsClt.webSocketClient.onopen = () => { clWsClt.webSocketClient.onopen = () => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' });
this.waitTime = 0.5; this.waitTime = 0.5;
}; };
clWsClt.webSocketClient.onclose = (e) => { clWsClt.webSocketClient.onclose = (e) => {
if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLT') { if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLN') {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...' });
clWsClt.webSocketClient.close(); clWsClt.webSocketClient.close();
if (clWsClt.reConnect) { this.reconnet(clWsClt); } if (clWsClt.reConnect) { this.reconnet(clWsClt); }
@ -79,7 +79,7 @@ export class CLWebSocketClient {
clWsClt.webSocketClient.onmessage = (msg) => { clWsClt.webSocketClient.onmessage = (msg) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data });
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data; msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
msg['source'] = 'CLT'; msg['source'] = 'CLN';
const msgStr = JSON.stringify(msg); const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode); this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
}; };
@ -100,7 +100,7 @@ export class CLWebSocketClient {
public disconnect = (selectedNode: CommonSelectedNode) => { public disconnect = (selectedNode: CommonSelectedNode) => {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index); const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.readyState === WebSocket.OPEN) { if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.readyState === WebSocket.OPEN) {
this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the CLightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the Core Lightning\'s Websocket Server..' });
clientExists.reConnect = false; clientExists.reConnect = false;
clientExists.webSocketClient.close(); clientExists.webSocketClient.close();
const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index); const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index);

View File

@ -13,6 +13,17 @@ export const arrangeFees = (selNode: CommonSelectedNode, body, current_time) =>
let fee = 0; let fee = 0;
body.relayed.forEach((relayedEle) => { body.relayed.forEach((relayedEle) => {
fee = Math.round((relayedEle.amountIn - relayedEle.amountOut) / 1000); fee = Math.round((relayedEle.amountIn - relayedEle.amountOut) / 1000);
if (relayedEle.timestamp) {
if (relayedEle.timestamp.unix) {
if ((relayedEle.timestamp.unix * 1000) >= day_start_time) {
fees.daily_fee = fees.daily_fee + fee;
fees.daily_txs = fees.daily_txs + 1;
}
if ((relayedEle.timestamp.unix * 1000) >= week_start_time) {
fees.weekly_fee = fees.weekly_fee + fee;
fees.weekly_txs = fees.weekly_txs + 1;
}
} else {
if (relayedEle.timestamp >= day_start_time) { if (relayedEle.timestamp >= day_start_time) {
fees.daily_fee = fees.daily_fee + fee; fees.daily_fee = fees.daily_fee + fee;
fees.daily_txs = fees.daily_txs + 1; fees.daily_txs = fees.daily_txs + 1;
@ -21,6 +32,8 @@ export const arrangeFees = (selNode: CommonSelectedNode, body, current_time) =>
fees.weekly_fee = fees.weekly_fee + fee; fees.weekly_fee = fees.weekly_fee + fee;
fees.weekly_txs = fees.weekly_txs + 1; fees.weekly_txs = fees.weekly_txs + 1;
} }
}
}
fees.monthly_fee = fees.monthly_fee + fee; fees.monthly_fee = fees.monthly_fee + fee;
fees.monthly_txs = fees.monthly_txs + 1; fees.monthly_txs = fees.monthly_txs + 1;
}); });

View File

@ -21,7 +21,7 @@ export const getSentInfoFromPaymentRequest = (selNode: CommonSelectedNode, payme
export const getQueryNodes = (selNode: CommonSelectedNode, nodeIds) => { export const getQueryNodes = (selNode: CommonSelectedNode, nodeIds) => {
options.url = selNode.ln_server_url + '/nodes'; options.url = selNode.ln_server_url + '/nodes';
options.form = { nodeIds: nodeIds }; options.form = { nodeIds: nodeIds.reduce((acc, curr) => acc + ',' + curr) };
return request.post(options).then((nodes) => { return request.post(options).then((nodes) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes }); logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes });
return nodes; return nodes;
@ -72,21 +72,24 @@ export const queryPaymentRoute = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form });
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body });
if (body && body.length) { if (body && body.routes && body.routes.length) {
const queryRoutes = []; let allRoutesNodeIds = [];
return getQueryNodes(req.session.selectedNode, body).then((hopsWithAlias) => { allRoutesNodeIds = body.routes.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
return getQueryNodes(req.session.selectedNode, allRoutesNodeIds).then((nodesWithAlias) => {
let foundPeer = null; let foundPeer = null;
body.map((hop) => { body.routes.forEach((route, i) => {
foundPeer = hopsWithAlias.find((hopWithAlias) => hop === hopWithAlias.nodeId); route.nodeIds.map((node, j) => {
queryRoutes.push({ nodeId: hop, alias: foundPeer ? foundPeer.alias : '' }); foundPeer = nodesWithAlias.find((nodeWithAlias) => node === nodeWithAlias.nodeId);
return hop; body.routes[i].nodeIds[j] = { nodeId: node, alias: foundPeer ? foundPeer.alias : '' };
return node;
}); });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Query Routes with Alias Received', data: queryRoutes }); });
res.status(200).json(queryRoutes); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Query Routes with Alias Received', data: body });
res.status(200).json(body);
}); });
} else { } else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Empty Payment Route Information Received' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Empty Payment Route Information Received' });
res.status(200).json([]); res.status(200).json({ routes: [] });
} }
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'Query Route Error', req.session.selectedNode); const err = common.handleError(errRes, 'Payments', 'Query Route Error', req.session.selectedNode);

View File

@ -19,7 +19,7 @@ export const getFees = (req, res, next) => {
const month_start_time = (Math.round(start_date.getTime() / 1000)); const month_start_time = (Math.round(start_date.getTime() / 1000));
const week_start_time = current_time - 604800; const week_start_time = current_time - 604800;
const day_start_time = current_time - 86400; const day_start_time = current_time - 86400;
return getAllForwardingEvents(req, month_start_time, current_time, 0, (history) => { return getAllForwardingEvents(req, month_start_time, current_time, 0, 'fees', (history) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Forwarding History Received', data: history }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Forwarding History Received', data: history });
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 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 weekly_sum = history.forwarding_events.reduce((acc, curr) => ((curr.timestamp >= week_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);

View File

@ -8,11 +8,16 @@ const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
const lndWsClient: LNDWebSocketClient = LNDWSClient; const lndWsClient: LNDWebSocketClient = LNDWSClient;
export const getInvoice = (req, res, next) => { export const invoiceLookup = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Getting Invoice Information..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Getting Invoice Information..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/' + req.params.rHashStr; options.url = req.session.selectedNode.ln_server_url + '/v2/invoices/lookup';
if (req.query.payment_addr) {
options.url = options.url + '?payment_addr=' + req.query.payment_addr;
} else {
options.url = options.url + '?payment_hash=' + req.query.payment_hash;
}
request(options).then((body) => { request(options).then((body) => {
body.r_preimage = body.r_preimage ? Buffer.from(body.r_preimage, 'base64').toString('hex') : ''; body.r_preimage = body.r_preimage ? Buffer.from(body.r_preimage, 'base64').toString('hex') : '';
body.r_hash = body.r_hash ? Buffer.from(body.r_hash, 'base64').toString('hex') : ''; body.r_hash = body.r_hash ? Buffer.from(body.r_hash, 'base64').toString('hex') : '';
@ -20,7 +25,7 @@ export const getInvoice = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Information Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Information Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Invoices', 'Get Invoice Error', req.session.selectedNode); const err = common.handleError(errRes, 'Invoices', 'Invoice Lookup Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };

View File

@ -2,6 +2,7 @@ import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js'; import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { CommonSelectedNode } from '../../models/config.model.js'; import { CommonSelectedNode } from '../../models/config.model.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
@ -71,15 +72,30 @@ export const getAllLightningTransactions = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Getting All Lightning Transactions..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Getting All Lightning Transactions..' });
const options1 = JSON.parse(JSON.stringify(common.getOptions(req))); const options1 = JSON.parse(JSON.stringify(common.getOptions(req)));
const options2 = JSON.parse(JSON.stringify(common.getOptions(req))); const options2 = JSON.parse(JSON.stringify(common.getOptions(req)));
options1.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=100000&index_offset=0&reversed=true'; // options1.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=100000&index_offset=0&reversed=true';
options2.url = req.session.selectedNode.ln_server_url + '/v1/invoices?num_max_invoices=100000&index_offset=0&reversed=true'; options2.url = req.session.selectedNode.ln_server_url + '/v1/invoices?num_max_invoices=100000&index_offset=0&reversed=true';
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Payments Options', data: options1 }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Payments Options', data: options1 });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Invoices Options', data: options2 }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Invoices Options', data: options2 });
return Promise.all([request(options1), request(options2)]).then((values) => { // return Promise.all([request(options1), request(options2)]).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'All Lightning Transactions Received', data: ({ totalPayments: values[0].length || 0, totalInvoices: values[1].length || 0 }) }); return Promise.all([{ payments: [] }, request(options2)]).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'All Lightning Transactions Received', data: ({ totalPayments: values[0].payments.length || 0, totalInvoices: values[1].invoices.length || 0 }) });
res.status(200).json({ listPaymentsAll: values[0], listInvoicesAll: values[1] }); res.status(200).json({ listPaymentsAll: values[0], listInvoicesAll: values[1] });
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'All Lightning Transactions Error', req.session.selectedNode); const err = common.handleError(errRes, 'Payments', 'All Lightning Transactions Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const paymentLookup = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Looking up Payment..' });
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v2/router/track/' + req.params.paymentHash;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Information Received for ' + req.params.paymentHash, data: body });
res.status(200).json(body.result || body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'Payment Lookup Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

View File

@ -4,11 +4,11 @@ import { Common, CommonService } from '../../utils/common.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
let responseData = { forwarding_events: [], last_offset_index: 0 }; const responseData = { switch: { forwarding_events: [], last_offset_index: 0 }, fees: { forwarding_events: [], last_offset_index: 0 } };
const num_max_events = 100; const num_max_events = 100;
export const forwardingHistory = (req, res, next) => { export const forwardingHistory = (req, res, next) => {
getAllForwardingEvents(req, req.body.start_time, req.body.end_time, 0, (eventsResponse) => { getAllForwardingEvents(req, req.body.start_time, req.body.end_time, 0, 'switch', (eventsResponse) => {
if (eventsResponse.error) { if (eventsResponse.error) {
res.status(eventsResponse.error.statusCode).json(eventsResponse); res.status(eventsResponse.error.statusCode).json(eventsResponse);
} else { } else {
@ -17,9 +17,9 @@ export const forwardingHistory = (req, res, next) => {
}); });
}; };
export const getAllForwardingEvents = (req, start, end, offset, callback) => { export const getAllForwardingEvents = (req, start, end, offset, caller, callback) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Getting Forwarding Events..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Getting Forwarding Events..' });
if (offset === 0) { responseData = { forwarding_events: [], last_offset_index: 0 }; } if (offset === 0) { responseData[caller] = { forwarding_events: [], last_offset_index: 0 }; }
if (!req.session.selectedNode) { if (!req.session.selectedNode) {
const err = common.handleError({ message: 'Session Expired after a day\'s inactivity.', statusCode: 401 }, 'Balance', 'Get Balance Error', req.session.selectedNode); const err = common.handleError({ message: 'Session Expired after a day\'s inactivity.', statusCode: 401 }, 'Balance', 'Get Balance Error', req.session.selectedNode);
return callback({ message: err.message, error: err.error, statusCode: err.statusCode }); return callback({ message: err.message, error: err.error, statusCode: err.statusCode });
@ -35,16 +35,17 @@ export const getAllForwardingEvents = (req, start, end, offset, callback) => {
return request.post(options).then((body) => { return request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Forwarding Events Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Switch', msg: 'Forwarding Events Received', data: body });
if (body.forwarding_events) { if (body.forwarding_events) {
responseData.forwarding_events.push(...body.forwarding_events); responseData[caller].forwarding_events.push(...body.forwarding_events);
responseData[caller].last_offset_index = body.last_offset_index ? body.last_offset_index : 0;
} }
if (!body.last_offset_index || body.last_offset_index < offset + num_max_events) { if (!body.last_offset_index || body.last_offset_index < offset + num_max_events) {
responseData.last_offset_index = body.last_offset_index ? body.last_offset_index : 0; responseData[caller].last_offset_index = body.last_offset_index ? body.last_offset_index : 0;
if (responseData.forwarding_events) { if (responseData[caller].forwarding_events) {
responseData.forwarding_events = common.sortDescByKey(responseData.forwarding_events, 'timestamp'); responseData[caller].forwarding_events = common.sortDescByKey(responseData[caller].forwarding_events, 'timestamp');
} }
return callback(responseData); return callback(responseData[caller]);
} else { } else {
return getAllForwardingEvents(req, start, end, offset + num_max_events, callback); return getAllForwardingEvents(req, start, end, offset + num_max_events, caller, callback);
} }
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Switch', 'Get All Forwarding Events Error', req.session.selectedNode); const err = common.handleError(errRes, 'Switch', 'Get All Forwarding Events Error', req.session.selectedNode);

View File

@ -80,6 +80,23 @@ export class LNDWebSocketClient {
}); });
}; };
public subscribeToPayment = (options: any, selectedNode: CommonSelectedNode, paymentHash: string) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Payment ' + paymentHash + ' ..' });
options.url = selectedNode.ln_server_url + '/v2/router/track/' + paymentHash;
request(options).then((msg) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Information Received for ' + paymentHash });
msg['type'] = 'payment';
msg['source'] = 'LND';
const msgStr = JSON.stringify(msg);
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Info Received', data: msgStr });
this.wsServer.sendEventsToAllLNClients(msgStr, selectedNode);
}).catch((errRes) => {
const err = this.common.handleError(errRes, 'Payment', 'Subscribe to Payment Error for ' + paymentHash, selectedNode);
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message + ' ' + paymentHash }) : (typeof err === 'object') ? JSON.stringify({ error: err + ' ' + paymentHash }) : ('{ "error": ' + err + ' ' + paymentHash + ' }'));
this.wsServer.sendErrorToAllLNClients(errStr, selectedNode);
});
};
public setOptionsForSelNode = (selectedNode: CommonSelectedNode) => { public setOptionsForSelNode = (selectedNode: CommonSelectedNode) => {
const options = { url: '', rejectUnauthorized: false, json: true, form: null }; const options = { url: '', rejectUnauthorized: false, json: true, form: null };
try { try {

View File

@ -56,8 +56,7 @@ export const authenticateUser = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated' });
res.status(406).json({ message: 'SSO Authentication Error', error: 'Login with Password is not allowed with SSO.' }); res.status(406).json({ message: 'SSO Authentication Error', error: 'Login with Password is not allowed with SSO.' });
} else if (req.body.authenticateWith === 'PASSWORD') { } else if (req.body.authenticateWith === 'PASSWORD') {
const cookieValue = common.readCookie(); if (common.cookie_value.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(common.cookie_value).digest('hex'), 'utf-8'), Buffer.from(req.body.authenticationValue, 'utf-8'))) {
if (cookieValue.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(cookieValue).digest('hex'), 'utf-8'), Buffer.from(req.body.authenticationValue, 'utf-8'))) {
common.refreshCookie(); common.refreshCookie();
if (!req.session.selectedNode) { req.session.selectedNode = common.initSelectedNode; } if (!req.session.selectedNode) { req.session.selectedNode = common.initSelectedNode; }
const token = jwt.sign({ user: 'SSO_USER' }, common.secret_key); const token = jwt.sign({ user: 'SSO_USER' }, common.secret_key);

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getBalance } from '../../controllers/c-lightning/balance.js'; import { getBalance } from '../../controllers/cln/balance.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards } from '../../controllers/c-lightning/channels.js'; import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards } from '../../controllers/cln/channels.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getFees } from '../../controllers/c-lightning/fees.js'; import { getFees } from '../../controllers/cln/fees.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getInfo } from '../../controllers/c-lightning/getInfo.js'; import { getInfo } from '../../controllers/cln/getInfo.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listInvoices, addInvoice, deleteExpiredInvoice } from '../../controllers/c-lightning/invoices.js'; import { listInvoices, addInvoice, deleteExpiredInvoice } from '../../controllers/cln/invoices.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { signMessage, verifyMessage } from '../../controllers/c-lightning/message.js'; import { signMessage, verifyMessage } from '../../controllers/cln/message.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { getRoute, listNode, listChannel, feeRates } from '../../controllers/c-lightning/network.js'; import { getRoute, listNode, listChannel, feeRates } from '../../controllers/cln/network.js';
const router = Router(); const router = Router();

View File

@ -1,7 +1,7 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/c-lightning/offers.js'; import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/cln/offers.js';
const router = Router(); const router = Router();

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