import { Injectable, OnDestroy } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Subject, of } from 'rxjs'; import { map, mergeMap, catchError, withLatestFrom, takeUntil } from 'rxjs/operators'; import { Location } from '@angular/common'; import { environment, API_URL } from '../../../environments/environment'; import { LoggerService } from '../../shared/services/logger.service'; import { SessionService } from '../../shared/services/session.service'; import { CommonService } from '../../shared/services/common.service'; import { ErrorMessageComponent } from '../../shared/components/data-modal/error-message/error-message.component'; import { CLInvoiceInformationComponent } from '../transactions/invoice-information-modal/invoice-information.component'; import { GetInfo, Fees, Balance, LocalRemoteBalance, Payment, FeeRates, ListInvoices, Invoice, Peer } from '../../shared/models/clModels'; import * as fromRTLReducer from '../../store/rtl.reducers'; import * as RTLActions from '../../store/rtl.actions'; import * as CLActions from './cl.actions'; import * as fromCLReducers from '../store/cl.reducers'; import { AlertTypeEnum, CurrencyUnitEnum } from '../../shared/services/consts-enums-functions'; @Injectable() export class CLEffects implements OnDestroy { CHILD_API_URL = API_URL + '/cl'; private unSubs: Array> = [new Subject(), new Subject()]; constructor( private actions$: Actions, private httpClient: HttpClient, private store: Store, private sessionService: SessionService, private logger: LoggerService, private commonService: CommonService, private router: Router, private location: Location) { this.store.select('cl') .pipe(takeUntil(this.unSubs[0])) .subscribe((rtlStore) => { if(rtlStore.initialAPIResponseStatus[0] === 'INCOMPLETE' && rtlStore.initialAPIResponseStatus.length > 9) { // Num of Initial APIs + 1 rtlStore.initialAPIResponseStatus[0] = 'COMPLETE'; this.store.dispatch(new RTLActions.CloseSpinner()); } }); } @Effect() infoFetchCL = this.actions$.pipe( ofType(CLActions.FETCH_INFO_CL), withLatestFrom(this.store.select('root')), mergeMap(([action, store]: [CLActions.FetchInfo, fromRTLReducer.RootState]) => { this.store.dispatch(new CLActions.ClearEffectError('FetchInfo')); return this.httpClient.get(this.CHILD_API_URL + environment.GETINFO_API) .pipe( takeUntil(this.actions$.pipe(ofType(RTLActions.SET_SELECTED_NODE))), map((info) => { this.logger.info(info); if (info.chains && info.chains.length && info.chains[0] && (typeof info.chains[0] === 'object' && info.chains[0].hasOwnProperty('chain') && info.chains[0].chain.toLowerCase().indexOf('bitcoin') < 0) ) { this.store.dispatch(new RTLActions.CloseAllDialogs()); this.store.dispatch(new RTLActions.OpenAlert({ data: { type: AlertTypeEnum.ERROR, alertTitle: 'Shitcoin Found', titleMessage: 'Sorry Not Sorry, RTL is Bitcoin Only!' }})); return { type: RTLActions.LOGOUT }; } else { this.initializeRemainingData(info, action.payload.loadPage); return { type: CLActions.SET_INFO_CL, payload: info ? info : {} }; } }), catchError((err) => { const code = (err.error && err.error.error && err.error.error.message && err.error.error.message.code) ? err.error.error.message.code : (err.error && err.error.error && err.error.error.code) ? err.error.error.code : err.status ? err.status : ''; const message = ((err.error && err.error.message) ? err.error.message + ' ' : '') + ((err.error && err.error.error && err.error.error.error && typeof err.error.error.error === 'string') ? err.error.error.error : (err.error && err.error.error && err.error.error.errno && typeof err.error.error.errno === 'string') ? err.error.error.errno : (err.error && err.error.error && typeof err.error.error === 'string') ? err.error.error : (err.error && typeof err.error === 'string') ? err.error : 'Unknown Error'); this.router.navigate(['/error'], { state: { errorCode: code, errorMessage: message }}); this.handleErrorWithoutAlert('FetchInfo', 'Fetching Node Info Failed.', err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() fetchFeesCL = this.actions$.pipe( ofType(CLActions.FETCH_FEES_CL), mergeMap((action: CLActions.FetchFees) => { this.store.dispatch(new CLActions.ClearEffectError('FetchFees')); return this.httpClient.get(this.CHILD_API_URL + environment.FEES_API); }), map((fees) => { this.logger.info(fees); return { type: CLActions.SET_FEES_CL, payload: fees ? fees : {} }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchFees', 'Fetching Fees Failed.', err); return of({type: RTLActions.VOID}); } )); @Effect() fetchFeeRatesCL = this.actions$.pipe( ofType(CLActions.FETCH_FEE_RATES_CL), mergeMap((action: CLActions.FetchFeeRates) => { this.store.dispatch(new CLActions.ClearEffectError('FetchFeeRates')); return this.httpClient.get(this.CHILD_API_URL + environment.NETWORK_API + '/feeRates/' + action.payload); }), map((feeRates) => { this.logger.info(feeRates); return { type: CLActions.SET_FEE_RATES_CL, payload: feeRates ? feeRates : {} }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchFeeRates', 'Fetching Fee Rates Failed.', err); return of({type: RTLActions.VOID}); } )); @Effect() fetchBalanceCL = this.actions$.pipe( ofType(CLActions.FETCH_BALANCE_CL), mergeMap((action: CLActions.FetchBalance) => { this.store.dispatch(new CLActions.ClearEffectError('FetchBalance')); return this.httpClient.get(this.CHILD_API_URL + environment.BALANCE_API); }), map((balance) => { this.logger.info(balance); return { type: CLActions.SET_BALANCE_CL, payload: balance ? balance : {} }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchBalance', 'Fetching Balances Failed.', err); return of({type: RTLActions.VOID}); } )); @Effect() fetchLocalRemoteBalanceCL = this.actions$.pipe( ofType(CLActions.FETCH_LOCAL_REMOTE_BALANCE_CL), mergeMap((action: CLActions.FetchLocalRemoteBalance) => { this.store.dispatch(new CLActions.ClearEffectError('FetchLocalRemoteBalance')); return this.httpClient.get(this.CHILD_API_URL + environment.CHANNELS_API + '/localremotebalance'); }), map((lrBalance) => { this.logger.info(lrBalance); return { type: CLActions.SET_LOCAL_REMOTE_BALANCE_CL, payload: lrBalance ? lrBalance : {} }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchLocalRemoteBalance', 'Fetching Balances Failed.', err); return of({type: RTLActions.VOID}); } )); @Effect() getNewAddressCL = this.actions$.pipe( ofType(CLActions.GET_NEW_ADDRESS_CL), mergeMap((action: CLActions.GetNewAddress) => { return this.httpClient.get(this.CHILD_API_URL + environment.ON_CHAIN_API + '?type=' + action.payload.addressCode) .pipe(map((newAddress: any) => { this.logger.info(newAddress); this.store.dispatch(new RTLActions.CloseSpinner()); return { type: CLActions.SET_NEW_ADDRESS_CL, payload: (newAddress && newAddress.address) ? newAddress.address : {} }; }), catchError((err: any) => { this.handleErrorWithAlert('ERROR', 'Generate New Address Failed', this.CHILD_API_URL + environment.ON_CHAIN_API + '?type=' + action.payload.addressId, err); return of({type: RTLActions.VOID}); })); }) ); @Effect({ dispatch: false }) setNewAddressCL = this.actions$.pipe( ofType(CLActions.SET_NEW_ADDRESS_CL), map((action: CLActions.SetNewAddress) => { this.logger.info(action.payload); return action.payload; }) ); @Effect() peersFetchCL = this.actions$.pipe( ofType(CLActions.FETCH_PEERS_CL), mergeMap((action: CLActions.FetchPeers) => { this.store.dispatch(new CLActions.ClearEffectError('FetchPeers')); return this.httpClient.get(this.CHILD_API_URL + environment.PEERS_API) .pipe( map((peers: any) => { this.logger.info(peers); return { type: CLActions.SET_PEERS_CL , payload: peers ? peers : [] }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchPeers', 'Fetching Peers Failed.', err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() saveNewPeerCL = this.actions$.pipe( ofType(CLActions.SAVE_NEW_PEER_CL), withLatestFrom(this.store.select('cl')), mergeMap(([action, clData]: [CLActions.SaveNewPeer, fromCLReducers.CLState]) => { this.store.dispatch(new CLActions.ClearEffectError('SaveNewPeer')); return this.httpClient.post(this.CHILD_API_URL + environment.PEERS_API, { id: action.payload.id }) .pipe( map((postRes: Peer[]) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new CLActions.SetPeers((postRes && postRes.length > 0) ? postRes : [])); return { type: CLActions.NEWLY_ADDED_PEER_CL, payload: {peer: postRes.find(peer => action.payload.id.indexOf(peer.id) === 0)} }; }), catchError((err: any) => { this.handleErrorWithoutAlert('SaveNewPeer', 'Peer Connection Failed.', err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() detachPeerCL = this.actions$.pipe( ofType(CLActions.DETACH_PEER_CL), mergeMap((action: CLActions.DetachPeer) => { return this.httpClient.delete(this.CHILD_API_URL + environment.PEERS_API + '/' + action.payload.id + '?force=' + action.payload.force) .pipe( map((postRes: any) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new RTLActions.OpenSnackBar('Peer Disconnected Successfully!')); return { type: CLActions.REMOVE_PEER_CL, payload: { id: action.payload.id } }; }), catchError((err: any) => { this.handleErrorWithAlert('ERROR', 'Unable to Detach Peer. Try again later.', this.CHILD_API_URL + environment.PEERS_API + '/' + action.payload.id, err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() channelsFetchCL = this.actions$.pipe( ofType(CLActions.FETCH_CHANNELS_CL), mergeMap((action: CLActions.FetchChannels) => { this.store.dispatch(new CLActions.ClearEffectError('FetchChannels')); return this.httpClient.get(this.CHILD_API_URL + environment.CHANNELS_API + '/listChannels') .pipe( map((channels: any) => { this.logger.info(channels); this.store.dispatch(new CLActions.GetForwardingHistory()); return { type: CLActions.SET_CHANNELS_CL, payload: (channels && channels.length > 0) ? channels : [] }; }, catchError((err: any) => { this.handleErrorWithoutAlert('FetchChannels', 'Fetching Channels Failed.', err); return of({type: RTLActions.VOID}); }) )); } )); @Effect() openNewChannelCL = this.actions$.pipe( ofType(CLActions.SAVE_NEW_CHANNEL_CL), mergeMap((action: CLActions.SaveNewChannel) => { this.store.dispatch(new CLActions.ClearEffectError('SaveNewChannel')); let newPayload = {id: action.payload.peerId, satoshis: action.payload.satoshis, feeRate: action.payload.feeRate, announce: action.payload.announce, minconf: (action.payload.minconf) ? action.payload.minconf : null}; if (action.payload.utxos) { newPayload['utxos'] = action.payload.utxos; } return this.httpClient.post(this.CHILD_API_URL + environment.CHANNELS_API, newPayload) .pipe(map((postRes: any) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new RTLActions.OpenSnackBar('Channel Added Successfully!')); this.store.dispatch(new CLActions.FetchBalance()); this.store.dispatch(new CLActions.FetchUTXOs()); return { type: CLActions.FETCH_CHANNELS_CL }; }), catchError((err: any) => { this.handleErrorWithoutAlert('SaveNewChannel', 'Opening Channel Failed.', err); return of({type: RTLActions.VOID}); })); } )); @Effect() updateChannelCL = this.actions$.pipe( ofType(CLActions.UPDATE_CHANNELS_CL), mergeMap((action: CLActions.UpdateChannels) => { return this.httpClient.post(this.CHILD_API_URL + environment.CHANNELS_API + '/setChannelFee', { id: action.payload.channelId, base: action.payload.baseFeeMsat, ppm: action.payload.feeRate }) .pipe( map((postRes: any) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); if(action.payload.channelId === 'all') { this.store.dispatch(new RTLActions.OpenSnackBar({message:'All Channels Updated Successfully. Fee policy updates may take some time to reflect on the channel.', duration: 5000})); } else { this.store.dispatch(new RTLActions.OpenSnackBar({message:'Channel Updated Successfully. Fee policy updates may take some time to reflect on the channel.', duration: 5000})); } return { type: CLActions.FETCH_CHANNELS_CL }; }), catchError((err: any) => { this.handleErrorWithAlert('ERROR', 'Update Channel Failed', this.CHILD_API_URL + environment.CHANNELS_API, err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() closeChannelCL = this.actions$.pipe( ofType(CLActions.CLOSE_CHANNEL_CL), mergeMap((action: CLActions.CloseChannel) => { const queryParam = action.payload.force ? '?force=' + action.payload.force : ''; return this.httpClient.delete(this.CHILD_API_URL + environment.CHANNELS_API + '/' + action.payload.channelId + queryParam) .pipe( map((postRes: any) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new CLActions.FetchChannels()); this.store.dispatch(new RTLActions.OpenSnackBar('Channel Closed Successfully!')); return { type: CLActions.REMOVE_CHANNEL_CL, payload: { channelId: action.payload.channelId } }; }), catchError((err: any) => { this.handleErrorWithAlert('ERROR', 'Unable to Close Channel. Try again later.', this.CHILD_API_URL + environment.CHANNELS_API, err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() paymentsFetchCL = this.actions$.pipe( ofType(CLActions.FETCH_PAYMENTS_CL), mergeMap((action: CLActions.FetchPayments) => { this.store.dispatch(new CLActions.ClearEffectError('FetchPayments')); return this.httpClient.get(this.CHILD_API_URL + environment.PAYMENTS_API); }), map((payments) => { this.logger.info(payments); return { type: CLActions.SET_PAYMENTS_CL, payload: payments ? payments : [] }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchPayments', 'Fetching Payments Failed.', err); return of({type: RTLActions.VOID}); } )); @Effect() decodePaymentCL = this.actions$.pipe( ofType(CLActions.DECODE_PAYMENT_CL), mergeMap((action: CLActions.DecodePayment) => { this.store.dispatch(new CLActions.ClearEffectError('DecodePayment')); return this.httpClient.get(this.CHILD_API_URL + environment.PAYMENTS_API + '/' + action.payload.routeParam) .pipe( map((decodedPayment) => { this.logger.info(decodedPayment); this.store.dispatch(new RTLActions.CloseSpinner()); return { type: CLActions.SET_DECODED_PAYMENT_CL, payload: decodedPayment ? decodedPayment : {} }; }), catchError((err: any) => { if (action.payload.fromDialog) { this.handleErrorWithoutAlert('DecodePayment', 'Decode Payment Failed.', err); } else { this.handleErrorWithAlert('ERROR', 'Decode Payment Failed', this.CHILD_API_URL + environment.PAYMENTS_API + '/' + action.payload, err); } return of({type: RTLActions.VOID}); }) ); }) ); @Effect({ dispatch: false }) setDecodedPaymentCL = this.actions$.pipe( ofType(CLActions.SET_DECODED_PAYMENT_CL), map((action: CLActions.SetDecodedPayment) => { this.logger.info(action.payload); return action.payload; }) ); @Effect() sendPaymentCL = this.actions$.pipe( ofType(CLActions.SEND_PAYMENT_CL), withLatestFrom(this.store.select('root')), mergeMap(([action, store]: [CLActions.SendPayment, any]) => { this.store.dispatch(new CLActions.ClearEffectError('SendPayment')); let paymentUrl = (action.payload.pubkey && action.payload.pubkey !== '') ? this.CHILD_API_URL + environment.PAYMENTS_API + '/keysend' : this.CHILD_API_URL + environment.PAYMENTS_API + '/invoice'; return this.httpClient.post(paymentUrl, action.payload).pipe( map((sendRes: any) => { this.logger.info(sendRes); this.store.dispatch(new RTLActions.CloseSpinner()); if (sendRes.error) { this.logger.error('Error: ' + sendRes.payment_error); const myErr = {status: sendRes.payment_error.status, error: sendRes.payment_error.error && sendRes.payment_error.error.error && typeof(sendRes.payment_error.error.error) === 'object' ? sendRes.payment_error.error.error : {error: sendRes.payment_error.error && sendRes.payment_error.error.error ? sendRes.payment_error.error.error : 'Unknown Error'}}; if (action.payload.fromDialog) { this.handleErrorWithoutAlert('SendPayment', 'Send Payment Failed.', myErr); } else { this.handleErrorWithAlert('ERROR', 'Send Payment Failed', this.CHILD_API_URL + environment.PAYMENTS_API, myErr); } return of({type: RTLActions.VOID}); } else { this.store.dispatch(new RTLActions.OpenSnackBar('Payment Sent Successfully!')); this.store.dispatch(new CLActions.FetchChannels()); this.store.dispatch(new CLActions.FetchBalance()); this.store.dispatch(new CLActions.FetchPayments()); this.store.dispatch(new CLActions.SetDecodedPayment({})); return { type: CLActions.SEND_PAYMENT_STATUS_CL, payload: sendRes }; } }), catchError((err: any) => { this.logger.error('Error: ' + JSON.stringify(err)); const myErr = {status: err.status, error: err.error && err.error.error && typeof(err.error.error) === 'object' ? err.error.error : {error: err.error && err.error.error ? err.error.error : 'Unknown Error'}}; if (action.payload.fromDialog) { this.handleErrorWithoutAlert('SendPayment', 'Send Payment Failed.', myErr); } else { this.handleErrorWithAlert('ERROR', 'Send Payment Failed', this.CHILD_API_URL + environment.PAYMENTS_API, myErr); } return of({type: RTLActions.VOID}); })); }) ); @Effect() queryRoutesFetchCL = this.actions$.pipe( ofType(CLActions.GET_QUERY_ROUTES_CL), mergeMap((action: CLActions.GetQueryRoutes) => { return this.httpClient.get(this.CHILD_API_URL + environment.NETWORK_API + '/getRoute/' + action.payload.destPubkey + '/' + action.payload.amount) .pipe( map((qrRes: any) => { this.logger.info(qrRes); return { type: CLActions.SET_QUERY_ROUTES_CL, payload: qrRes }; }), catchError((err: any) => { this.store.dispatch(new CLActions.SetQueryRoutes({ routes: [] })); this.handleErrorWithAlert('ERROR', 'Get Query Routes Failed', this.CHILD_API_URL + environment.NETWORK_API + '/getRoute/' + action.payload.destPubkey + '/' + action.payload.amount, err); return of({type: RTLActions.VOID}); }) ); } )); @Effect({ dispatch: false }) setQueryRoutesCL = this.actions$.pipe( ofType(CLActions.SET_QUERY_ROUTES_CL), map((action: CLActions.SetQueryRoutes) => { return action.payload; }) ); @Effect() peerLookupCL = this.actions$.pipe( ofType(CLActions.PEER_LOOKUP_CL), mergeMap((action: CLActions.PeerLookup) => { this.store.dispatch(new CLActions.ClearEffectError('Lookup')); return this.httpClient.get(this.CHILD_API_URL + environment.NETWORK_API + '/listNode/' + action.payload) .pipe( map((resPeer) => { this.logger.info(resPeer); this.store.dispatch(new RTLActions.CloseSpinner()); return { type: CLActions.SET_LOOKUP_CL, payload: resPeer }; }), catchError((err: any) => { this.store.dispatch(new CLActions.EffectError({ action: 'Lookup', code: err.status, message: err.error.message })); this.handleErrorWithAlert('ERROR', 'Peer Lookup Failed', this.CHILD_API_URL + environment.NETWORK_API + '/listNode/' + action.payload, err); return of({type: RTLActions.VOID}); }) ); }) ); @Effect() channelLookupCL = this.actions$.pipe( ofType(CLActions.CHANNEL_LOOKUP_CL), mergeMap((action: CLActions.ChannelLookup) => { this.store.dispatch(new CLActions.ClearEffectError('Lookup')); return this.httpClient.get(this.CHILD_API_URL + environment.NETWORK_API + '/listChannel/' + action.payload.shortChannelID) .pipe( map((resChannel) => { this.logger.info(resChannel); this.store.dispatch(new RTLActions.CloseSpinner()); return { type: CLActions.SET_LOOKUP_CL, payload: resChannel }; }), catchError((err: any) => { if(action.payload.showError) { this.store.dispatch(new CLActions.EffectError({ action: 'Lookup', code: err.status, message: err.error.message })); this.handleErrorWithAlert('ERROR', 'Channel Lookup Failed', this.CHILD_API_URL + environment.NETWORK_API + '/listChannel/' + action.payload.shortChannelID, err); } else { this.store.dispatch(new RTLActions.CloseSpinner()); } this.store.dispatch(new CLActions.SetLookup([])); return of({type: RTLActions.VOID}); }) ); }) ); @Effect() invoiceLookupCL = this.actions$.pipe( ofType(CLActions.INVOICE_LOOKUP_CL), mergeMap((action: CLActions.InvoiceLookup) => { this.store.dispatch(new CLActions.ClearEffectError('Lookup')); return this.httpClient.get(this.CHILD_API_URL + environment.INVOICES_API + '/listInvoice?label=' + action.payload) .pipe( map((resInvoice) => { this.logger.info(resInvoice); this.store.dispatch(new RTLActions.CloseSpinner()); return { type: CLActions.SET_LOOKUP_CL, payload: resInvoice }; }), catchError((err: any) => { this.store.dispatch(new CLActions.EffectError({ action: 'Lookup', code: err.status, message: err.error.message })); this.handleErrorWithAlert('ERROR', 'Invoice Lookup Failed', this.CHILD_API_URL + environment.NETWORK_API + '/listInvoice?label=' + action.payload, err); return of({type: RTLActions.VOID}); }) ); }) ); @Effect({ dispatch: false }) setLookupCL = this.actions$.pipe( ofType(CLActions.SET_LOOKUP_CL), map((action: CLActions.SetLookup) => { this.logger.info(action.payload); return action.payload; }) ); @Effect() fetchForwardingHistoryCL = this.actions$.pipe( ofType(CLActions.GET_FORWARDING_HISTORY_CL), mergeMap((action: CLActions.GetForwardingHistory) => { this.store.dispatch(new CLActions.ClearEffectError('GetForwardingHistory')); return this.httpClient.get(this.CHILD_API_URL + environment.CHANNELS_API + '/listForwards') .pipe( map((fhRes: any) => { this.logger.info(fhRes); return { type: CLActions.SET_FORWARDING_HISTORY_CL, payload: fhRes }; }), catchError((err: any) => { this.store.dispatch(new CLActions.EffectError({ action: 'GetForwardingHistory', code: err.status, message: err.error.error })); this.handleErrorWithAlert('ERROR', 'Get Forwarding History Failed', this.CHILD_API_URL + environment.CHANNELS_API + '/listForwards', err); return of({type: RTLActions.VOID}); }) ); }) ); @Effect() deleteExpiredInvoiceCL = this.actions$.pipe( ofType(CLActions.DELETE_EXPIRED_INVOICE_CL), mergeMap((action: CLActions.DeleteExpiredInvoice) => { const queryStr = (action.payload) ? '?maxexpiry=' + action.payload : ''; return this.httpClient.delete(this.CHILD_API_URL + environment.INVOICES_API + queryStr) .pipe( map((postRes: any) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new RTLActions.OpenSnackBar('Invoices Deleted Successfully!')); return { type: CLActions.FETCH_INVOICES_CL , payload: { num_max_invoices: 100, reversed: true } }; }), catchError((err: any) => { this.handleErrorWithAlert('ERROR', 'Delete Invoice Failed', this.CHILD_API_URL + environment.INVOICES_API, err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() saveNewInvoiceCL = this.actions$.pipe( ofType(CLActions.SAVE_NEW_INVOICE_CL), mergeMap((action: CLActions.SaveNewInvoice) => { this.store.dispatch(new CLActions.ClearEffectError('SaveNewInvoice')); return this.httpClient.post(this.CHILD_API_URL + environment.INVOICES_API, { label: action.payload.label, amount: action.payload.amount, description: action.payload.description, expiry: action.payload.expiry, private: action.payload.private }) .pipe( map((postRes: Invoice) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); postRes.msatoshi = action.payload.amount; postRes.label = action.payload.label; postRes.expires_at = Math.round((new Date().getTime() / 1000) + action.payload.expiry); postRes.expires_at_str = this.commonService.convertTimestampToDate(+postRes.expires_at); postRes.description = action.payload.description; postRes.status = 'unpaid'; this.store.dispatch(new RTLActions.OpenAlert({ data: { invoice: postRes, newlyAdded: false, component: CLInvoiceInformationComponent }})); return { type: CLActions.FETCH_INVOICES_CL , payload: { num_max_invoices: 100, reversed: true } }; }), catchError((err: any) => { this.handleErrorWithoutAlert('SaveNewInvoice', 'Add Invoice Failed.', err); return of({type: RTLActions.VOID}); }) ); } )); @Effect() invoicesFetchCL = this.actions$.pipe( ofType(CLActions.FETCH_INVOICES_CL), mergeMap((action: CLActions.FetchInvoices) => { this.store.dispatch(new CLActions.ClearEffectError('FetchInvoices')); const num_max_invoices = (action.payload.num_max_invoices) ? action.payload.num_max_invoices : 100; const index_offset = (action.payload.index_offset) ? action.payload.index_offset : 0; const reversed = (action.payload.reversed) ? action.payload.reversed : false; return this.httpClient.get(this.CHILD_API_URL + environment.INVOICES_API + '?num_max_invoices=' + num_max_invoices + '&index_offset=' + index_offset + '&reversed=' + reversed) .pipe(map((res: ListInvoices) => { this.logger.info(res); this.store.dispatch(new CLActions.SetTotalInvoices(res.invoices ? res.invoices.length : 0)); return { type: CLActions.SET_INVOICES_CL, payload: res }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchInvoices', 'Fetching Invoices Failed.', err); return of({type: RTLActions.VOID}); } )); })); @Effect() SetChannelTransactionCL = this.actions$.pipe( ofType(CLActions.SET_CHANNEL_TRANSACTION_CL), mergeMap((action: CLActions.SetChannelTransaction) => { this.store.dispatch(new CLActions.ClearEffectError('SetChannelTransaction')); return this.httpClient.post(this.CHILD_API_URL + environment.ON_CHAIN_API, action.payload) .pipe( map((postRes: any) => { this.logger.info(postRes); this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new CLActions.FetchBalance()); this.store.dispatch(new CLActions.FetchUTXOs()); return { type: CLActions.SET_CHANNEL_TRANSACTION_RES_CL, payload: postRes }; }), catchError((err: any) => { this.handleErrorWithoutAlert('SetChannelTransaction', 'Sending Fund Failed.', err); return of({type: RTLActions.VOID}); })); }) ); @Effect() utxosFetch = this.actions$.pipe( ofType(CLActions.FETCH_UTXOS_CL), mergeMap((action: CLActions.FetchUTXOs) => { this.store.dispatch(new CLActions.ClearEffectError('FetchUTXOs')); return this.httpClient.get(this.CHILD_API_URL + environment.ON_CHAIN_API + '/utxos'); }), map((utxos: any) => { this.logger.info(utxos); return { type: CLActions.SET_UTXOS_CL, payload: (utxos && utxos.outputs && utxos.outputs.length > 0) ? utxos.outputs : [] }; }), catchError((err: any) => { this.handleErrorWithoutAlert('FetchUTXOs', 'Fetching UTXOs Failed.', err); return of({type: RTLActions.VOID}); } )); initializeRemainingData(info: any, landingPage: string) { this.sessionService.setItem('clUnlocked', 'true'); const node_data = { identity_pubkey: info.id, alias: info.alias, testnet: (info.network.toLowerCase() === 'testnet') ? true : false, chains: info.chains, uris: info.uris, version: info.version, api_version: info.api_version, currency_unit: CurrencyUnitEnum.BTC, smaller_currency_unit: CurrencyUnitEnum.SATS, numberOfPendingChannels: info.num_pending_channels }; this.store.dispatch(new RTLActions.OpenSpinner('Initializing Node Data...')); this.store.dispatch(new RTLActions.SetNodeData(node_data)); this.store.dispatch(new CLActions.FetchFees()); this.store.dispatch(new CLActions.FetchChannels()); this.store.dispatch(new CLActions.FetchBalance()); this.store.dispatch(new CLActions.FetchLocalRemoteBalance()); this.store.dispatch(new CLActions.FetchFeeRates('perkw')); this.store.dispatch(new CLActions.FetchFeeRates('perkb')); this.store.dispatch(new CLActions.FetchPeers()); this.store.dispatch(new CLActions.FetchUTXOs()); this.store.dispatch(new CLActions.FetchPayments()); let newRoute = this.location.path(); if(newRoute.includes('/lnd/')) { newRoute = newRoute.replace('/lnd/', '/cl/'); } else if (newRoute.includes('/ecl/')) { newRoute = newRoute.replace('/ecl/', '/cl/'); } if (newRoute.includes('/login') || newRoute.includes('/error') || newRoute === '' || landingPage === 'HOME' || newRoute.includes('?access-key=')) { newRoute = '/cl/home'; } this.router.navigate([newRoute]); } handleErrorWithoutAlert(actionName: string, genericErrorMessage: string, err: { status: number, error: any }) { this.logger.error('ERROR IN: ' + actionName + '\n' + JSON.stringify(err)); if (err.status === 401) { this.logger.info('Redirecting to Login'); this.store.dispatch(new RTLActions.CloseAllDialogs()); this.store.dispatch(new RTLActions.Logout()); this.store.dispatch(new RTLActions.OpenSnackBar('Authentication Failed. Redirecting to Login.')); } else { this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new CLActions.EffectError({ action: actionName, code: err.status.toString(), message: (err.error.error && err.error.error.error && err.error.error.error.error && err.error.error.error.error.message && typeof err.error.error.error.error.message === 'string') ? err.error.error.error.error.message : (err.error.error && err.error.error.error && err.error.error.error.message && typeof err.error.error.error.message === 'string') ? err.error.error.error.message : (err.error.error && err.error.error.message && typeof err.error.error.message === 'string') ? err.error.error.message : (err.error.message && typeof err.error.message === 'string') ? err.error.message : typeof err.error === 'string' ? err.error : genericErrorMessage })); } } handleErrorWithAlert(alerType: string, alertTitle: string, errURL: string, err: { status: number, error: any }) { this.logger.error(err); if (err.status === 401) { this.logger.info('Redirecting to Login'); this.store.dispatch(new RTLActions.CloseAllDialogs()); this.store.dispatch(new RTLActions.Logout()); this.store.dispatch(new RTLActions.OpenSnackBar('Authentication Failed. Redirecting to Login.')); } else { this.store.dispatch(new RTLActions.CloseSpinner()); this.store.dispatch(new RTLActions.OpenAlert({ data: { type: alerType, alertTitle: alertTitle, message: { code: err.status, message: (err.error.error && err.error.error.error && err.error.error.error.error && err.error.error.error.error.message && typeof err.error.error.error.error.message === 'string') ? err.error.error.error.error.message : (err.error.error && err.error.error.error && err.error.error.error.message && typeof err.error.error.error.message === 'string') ? err.error.error.error.message : (err.error.error && err.error.error.message && typeof err.error.error.message === 'string') ? err.error.error.message : (err.error.message && typeof err.error.message === 'string') ? err.error.message : typeof err.error === 'string' ? err.error : 'Unknown Error', URL: errURL }, component: ErrorMessageComponent } })); } } ngOnDestroy() { this.unSubs.forEach(completeSub => { completeSub.next(); completeSub.complete(); }); } }