You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
688 lines
33 KiB
TypeScript
688 lines
33 KiB
TypeScript
import { Injectable, OnDestroy } from '@angular/core';
|
|
import { HttpClient } from '@angular/common/http';
|
|
import { Router } from '@angular/router';
|
|
import { Store } from '@ngrx/store';
|
|
import { Actions, createEffect, 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 { GetInfo, Channel, OnChainBalance, LightningBalance, ChannelsStatus, ChannelStats, Peer, Audit, Transaction, Invoice } from '../../shared/models/eclModels';
|
|
import { APICallStatusEnum, UI_MESSAGES } from '../../shared/services/consts-enums-functions';
|
|
import { ECLInvoiceInformationComponent } from '../transactions/invoice-information-modal/invoice-information.component';
|
|
|
|
import * as fromRTLReducer from '../../store/rtl.reducers';
|
|
import * as fromECLReducer from './ecl.reducers';
|
|
import * as ECLActions from './ecl.actions';
|
|
import * as RTLActions from '../../store/rtl.actions';
|
|
|
|
@Injectable()
|
|
export class ECLEffects implements OnDestroy {
|
|
|
|
CHILD_API_URL = API_URL + '/ecl';
|
|
private flgInitialized = false;
|
|
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
|
|
|
constructor(
|
|
private actions: Actions,
|
|
private httpClient: HttpClient,
|
|
private store: Store<fromRTLReducer.RTLState>,
|
|
private sessionService: SessionService,
|
|
private commonService: CommonService,
|
|
private logger: LoggerService,
|
|
private router: Router,
|
|
private location: Location
|
|
) {
|
|
this.store.select('ecl').
|
|
pipe(takeUntil(this.unSubs[0])).
|
|
subscribe((rtlStore) => {
|
|
if (
|
|
((rtlStore.apisCallStatus.FetchInfo.status === APICallStatusEnum.COMPLETED || rtlStore.apisCallStatus.FetchInfo.status === APICallStatusEnum.ERROR) &&
|
|
(rtlStore.apisCallStatus.FetchFees.status === APICallStatusEnum.COMPLETED || rtlStore.apisCallStatus.FetchFees.status === APICallStatusEnum.ERROR) &&
|
|
(rtlStore.apisCallStatus.FetchOnchainBalance.status === APICallStatusEnum.COMPLETED || rtlStore.apisCallStatus.FetchOnchainBalance.status === APICallStatusEnum.ERROR) &&
|
|
(rtlStore.apisCallStatus.FetchChannels.status === APICallStatusEnum.COMPLETED || rtlStore.apisCallStatus.FetchChannels.status === APICallStatusEnum.ERROR)) &&
|
|
!this.flgInitialized
|
|
) {
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.INITALIZE_NODE_DATA));
|
|
this.flgInitialized = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
infoFetchECL = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_INFO_ECL),
|
|
mergeMap((action: ECLActions.FetchInfo) => {
|
|
this.flgInitialized = false;
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.GET_NODE_INFO));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchInfo', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get<GetInfo>(this.CHILD_API_URL + environment.GETINFO_API).
|
|
pipe(
|
|
takeUntil(this.actions.pipe(ofType(RTLActions.SET_SELECTED_NODE))),
|
|
map((info) => {
|
|
this.logger.info(info);
|
|
this.initializeRemainingData(info, action.payload.loadPage);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchInfo', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.GET_NODE_INFO));
|
|
return {
|
|
type: ECLActions.SET_INFO_ECL,
|
|
payload: info ? info : {}
|
|
};
|
|
}),
|
|
catchError((err) => {
|
|
const code = this.commonService.extractErrorCode(err);
|
|
const msg = (code === 503) ? 'Unable to Connect to Eclair Server.' : this.commonService.extractErrorMessage(err);
|
|
this.router.navigate(['/error'], { state: { errorCode: code, errorMessage: msg } });
|
|
this.handleErrorWithoutAlert('FetchInfo', UI_MESSAGES.GET_NODE_INFO, 'Fetching Node Info Failed.', { status: code, error: msg });
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
fetchFees = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_FEES_ECL),
|
|
mergeMap((action: ECLActions.FetchFees) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchFees', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get<Audit>(this.CHILD_API_URL + environment.FEES_API + '/fees').
|
|
pipe(
|
|
map((fees: any) => {
|
|
this.logger.info(fees);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchFees', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_FEES_ECL,
|
|
payload: fees ? fees : {}
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchFees', UI_MESSAGES.NO_SPINNER, 'Fetching Fees Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
fetchPayments = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_PAYMENTS_ECL),
|
|
mergeMap((action: ECLActions.FetchPayments) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchPayments', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get<Audit>(this.CHILD_API_URL + environment.FEES_API + '/payments').
|
|
pipe(
|
|
map((payments: any) => {
|
|
this.logger.info(payments);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchPayments', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_PAYMENTS_ECL,
|
|
payload: payments ? payments : {}
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchPayments', UI_MESSAGES.NO_SPINNER, 'Fetching Payments Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
channelsFetch = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_CHANNELS_ECL),
|
|
mergeMap((action: ECLActions.FetchChannels) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchChannels', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get(this.CHILD_API_URL + environment.CHANNELS_API).
|
|
pipe(
|
|
map((channelsRes: { activeChannels: Channel[], pendingChannels: Channel[], inactiveChannels: Channel[], lightningBalances: LightningBalance, channelStatus: ChannelsStatus }) => {
|
|
this.logger.info(channelsRes);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchChannels', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new ECLActions.SetActiveChannels((channelsRes && channelsRes.activeChannels.length > 0) ? channelsRes.activeChannels : []));
|
|
this.store.dispatch(new ECLActions.SetPendingChannels((channelsRes && channelsRes.pendingChannels.length > 0) ? channelsRes.pendingChannels : []));
|
|
this.store.dispatch(new ECLActions.SetInactiveChannels((channelsRes && channelsRes.inactiveChannels.length > 0) ? channelsRes.inactiveChannels : []));
|
|
this.store.dispatch(new ECLActions.SetLightningBalance(channelsRes.lightningBalances));
|
|
if (action.payload && action.payload.fetchPayments) {
|
|
this.store.dispatch(new ECLActions.FetchPayments());
|
|
}
|
|
return {
|
|
type: ECLActions.SET_CHANNELS_STATUS_ECL,
|
|
payload: channelsRes.channelStatus
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchChannels', UI_MESSAGES.NO_SPINNER, 'Fetching Channels Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
channelStatsFetch = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_CHANNEL_STATS_ECL),
|
|
mergeMap((action: ECLActions.FetchChannelStats) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchChannelStats', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get(this.CHILD_API_URL + environment.CHANNELS_API + '/stats').
|
|
pipe(
|
|
map((channelStats: ChannelStats[]) => {
|
|
this.logger.info(channelStats);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchChannelStats', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_CHANNEL_STATS_ECL,
|
|
payload: (channelStats && channelStats.length > 0) ? channelStats : []
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchChannelStats', UI_MESSAGES.NO_SPINNER, 'Fetching Channel Stats Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
fetchOnchainBalance = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_ONCHAIN_BALANCE_ECL),
|
|
mergeMap((action: ECLActions.FetchOnchainBalance) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchOnchainBalance', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get<OnChainBalance>(this.CHILD_API_URL + environment.ON_CHAIN_API + '/balance');
|
|
}),
|
|
map((balance) => {
|
|
this.logger.info(balance);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchOnchainBalance', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_ONCHAIN_BALANCE_ECL,
|
|
payload: balance ? balance : {}
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchOnchainBalance', UI_MESSAGES.NO_SPINNER, 'Fetching Onchain Balances Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
));
|
|
|
|
peersFetch = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_PEERS_ECL),
|
|
mergeMap((action: ECLActions.FetchPeers) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchPeers', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get(this.CHILD_API_URL + environment.PEERS_API).
|
|
pipe(
|
|
map((peers: Peer[]) => {
|
|
this.logger.info(peers);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchPeers', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_PEERS_ECL,
|
|
payload: peers && peers.length ? peers : []
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchPeers', UI_MESSAGES.NO_SPINNER, 'Fetching Peers Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
getNewAddress = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.GET_NEW_ADDRESS_ECL),
|
|
mergeMap((action: ECLActions.GetNewAddress) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.GENERATE_NEW_ADDRESS));
|
|
return this.httpClient.get(this.CHILD_API_URL + environment.ON_CHAIN_API).
|
|
pipe(
|
|
map((newAddress: any) => {
|
|
this.logger.info(newAddress);
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.GENERATE_NEW_ADDRESS));
|
|
return {
|
|
type: ECLActions.SET_NEW_ADDRESS_ECL,
|
|
payload: newAddress
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithAlert('GetNewAddress', UI_MESSAGES.GENERATE_NEW_ADDRESS, 'Generate New Address Failed', this.CHILD_API_URL + environment.ON_CHAIN_API, err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
setNewAddress = createEffect(
|
|
() => this.actions.pipe(
|
|
ofType(ECLActions.SET_NEW_ADDRESS_ECL),
|
|
map((action: ECLActions.SetNewAddress) => {
|
|
this.logger.info(action.payload);
|
|
return action.payload;
|
|
})),
|
|
{ dispatch: false }
|
|
);
|
|
|
|
saveNewPeer = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.SAVE_NEW_PEER_ECL),
|
|
withLatestFrom(this.store.select('ecl')),
|
|
mergeMap(([action, eclData]: [ECLActions.SaveNewPeer, fromECLReducer.ECLState]) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.CONNECT_PEER));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SaveNewPeer', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.post(this.CHILD_API_URL + environment.PEERS_API + ((action.payload.id.includes('@') ? '?uri=' : '?nodeId=') + action.payload.id), {}).
|
|
pipe(
|
|
map((postRes: Peer[]) => {
|
|
this.logger.info(postRes);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SaveNewPeer', status: APICallStatusEnum.COMPLETED }));
|
|
postRes = (postRes && postRes.length) ? postRes : [];
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.CONNECT_PEER));
|
|
this.store.dispatch(new ECLActions.SetPeers(postRes));
|
|
return {
|
|
type: ECLActions.NEWLY_ADDED_PEER_ECL,
|
|
payload: { peer: postRes.find((peer) => peer.nodeId === (action.payload.id.includes('@') ? action.payload.id.substring(0, action.payload.id.indexOf('@')) : action.payload.id)) }
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('SaveNewPeer', UI_MESSAGES.CONNECT_PEER, 'Peer Connection Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
detachPeer = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.DETACH_PEER_ECL),
|
|
mergeMap((action: ECLActions.DisconnectPeer) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.DISCONNECT_PEER));
|
|
return this.httpClient.delete(this.CHILD_API_URL + environment.PEERS_API + '/' + action.payload.nodeId).
|
|
pipe(
|
|
map((postRes: any) => {
|
|
this.logger.info(postRes);
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.DISCONNECT_PEER));
|
|
this.store.dispatch(new RTLActions.OpenSnackBar('Disconnecting Peer!'));
|
|
return {
|
|
type: ECLActions.REMOVE_PEER_ECL,
|
|
payload: { nodeId: action.payload.nodeId }
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithAlert('DisconnectPeer', UI_MESSAGES.DISCONNECT_PEER, 'Unable to Detach Peer. Try again later.', this.CHILD_API_URL + environment.PEERS_API + '/' + action.payload.nodeId, err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
openNewChannel = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.SAVE_NEW_CHANNEL_ECL),
|
|
mergeMap((action: ECLActions.SaveNewChannel) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.OPEN_CHANNEL));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SaveNewChannel', status: APICallStatusEnum.INITIATED }));
|
|
const reqBody = action.payload.feeRate && action.payload.feeRate > 0 ?
|
|
{ nodeId: action.payload.nodeId, fundingSatoshis: action.payload.amount, channelFlags: +!action.payload.private, fundingFeerateSatByte: action.payload.feeRate } :
|
|
{ nodeId: action.payload.nodeId, fundingSatoshis: action.payload.amount, channelFlags: +!action.payload.private };
|
|
return this.httpClient.post(this.CHILD_API_URL + environment.CHANNELS_API, reqBody).
|
|
pipe(
|
|
map((postRes: any) => {
|
|
this.logger.info(postRes);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SaveNewChannel', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new ECLActions.FetchPeers());
|
|
this.store.dispatch(new ECLActions.FetchOnchainBalance());
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.OPEN_CHANNEL));
|
|
this.store.dispatch(new RTLActions.OpenSnackBar('Channel Added Successfully!'));
|
|
return {
|
|
type: ECLActions.FETCH_CHANNELS_ECL,
|
|
payload: { fetchPayments: false }
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('SaveNewChannel', UI_MESSAGES.OPEN_CHANNEL, 'Opening Channel Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
updateChannel = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.UPDATE_CHANNELS_ECL),
|
|
mergeMap((action: ECLActions.UpdateChannels) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.UPDATE_CHAN_POLICY));
|
|
let queryParam = '?feeBaseMsat=' + action.payload.baseFeeMsat + '&feeProportionalMillionths=' + action.payload.feeRate;
|
|
if (action.payload.channelIds) {
|
|
queryParam = queryParam + '&channelIds=' + action.payload.channelIds;
|
|
} else {
|
|
queryParam = queryParam + '&channelId=' + action.payload.channelId;
|
|
}
|
|
return this.httpClient.post(this.CHILD_API_URL + environment.CHANNELS_API + '/updateRelayFee' + queryParam, {}).
|
|
pipe(
|
|
map((postRes: any) => {
|
|
this.logger.info(postRes);
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.UPDATE_CHAN_POLICY));
|
|
if (action.payload.channelIds) {
|
|
this.store.dispatch(new RTLActions.OpenSnackBar('Channels Updated Successfully.'));
|
|
} else {
|
|
this.store.dispatch(new RTLActions.OpenSnackBar('Channel Updated Successfully!'));
|
|
}
|
|
return {
|
|
type: ECLActions.FETCH_CHANNELS_ECL,
|
|
payload: { fetchPayments: false }
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithAlert('UpdateChannels', UI_MESSAGES.UPDATE_CHAN_POLICY, 'Update Channel Failed', this.CHILD_API_URL + environment.CHANNELS_API, err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
closeChannel = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.CLOSE_CHANNEL_ECL),
|
|
mergeMap((action: ECLActions.CloseChannel) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner((action.payload.force) ? UI_MESSAGES.FORCE_CLOSE_CHANNEL : UI_MESSAGES.CLOSE_CHANNEL));
|
|
return this.httpClient.delete(this.CHILD_API_URL + environment.CHANNELS_API + '?channelId=' + action.payload.channelId + '&force=' + action.payload.force).
|
|
pipe(
|
|
map((postRes: any) => {
|
|
this.logger.info(postRes);
|
|
setTimeout(() => {
|
|
this.store.dispatch(new RTLActions.CloseSpinner((action.payload.force) ? UI_MESSAGES.FORCE_CLOSE_CHANNEL : UI_MESSAGES.CLOSE_CHANNEL));
|
|
this.store.dispatch(new ECLActions.FetchChannels({ fetchPayments: false }));
|
|
this.store.dispatch(new RTLActions.OpenSnackBar(action.payload.force ? 'Channel Force Closed Successfully!' : 'Channel Closed Successfully!'));
|
|
}, 2000);
|
|
return {
|
|
type: RTLActions.VOID
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithAlert('CloseChannel', ((action.payload.force) ? UI_MESSAGES.FORCE_CLOSE_CHANNEL : UI_MESSAGES.CLOSE_CHANNEL), 'Unable to Close Channel. Try again later.', this.CHILD_API_URL + environment.CHANNELS_API, err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
queryRoutesFetch = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.GET_QUERY_ROUTES_ECL),
|
|
mergeMap((action: ECLActions.GetQueryRoutes) => this.httpClient.get(this.CHILD_API_URL + environment.PAYMENTS_API + '/route?nodeId=' + action.payload.nodeId + '&amountMsat=' + action.payload.amount).
|
|
pipe(
|
|
map((qrRes: any) => {
|
|
this.logger.info(qrRes);
|
|
return {
|
|
type: ECLActions.SET_QUERY_ROUTES_ECL,
|
|
payload: qrRes
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.store.dispatch(new ECLActions.SetQueryRoutes([]));
|
|
this.handleErrorWithAlert('GetQueryRoutes', UI_MESSAGES.NO_SPINNER, 'Get Query Routes Failed', this.CHILD_API_URL + environment.PAYMENTS_API + '/route?nodeId=' + action.payload.nodeId + '&amountMsat=' + action.payload.amount, err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
))
|
|
));
|
|
|
|
setQueryRoutes = createEffect(
|
|
() => this.actions.pipe(
|
|
ofType(ECLActions.SET_QUERY_ROUTES_ECL),
|
|
map((action: ECLActions.SetQueryRoutes) => action.payload)),
|
|
{ dispatch: false }
|
|
);
|
|
|
|
sendPayment = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.SEND_PAYMENT_ECL),
|
|
withLatestFrom(this.store.select('root')),
|
|
mergeMap(([action, store]: [ECLActions.SendPayment, any]) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.SEND_PAYMENT));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SendPayment', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.post(this.CHILD_API_URL + environment.PAYMENTS_API, action.payload).
|
|
pipe(
|
|
map((sendRes: any) => {
|
|
this.logger.info(sendRes);
|
|
setTimeout(() => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SendPayment', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new ECLActions.SendPaymentStatus(sendRes));
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.SEND_PAYMENT));
|
|
this.store.dispatch(new ECLActions.FetchChannels({ fetchPayments: true }));
|
|
this.store.dispatch(new ECLActions.FetchPayments());
|
|
this.store.dispatch(new RTLActions.OpenSnackBar('Payment Submitted!'));
|
|
}, 3000);
|
|
return { type: RTLActions.VOID };
|
|
}),
|
|
catchError((err: any) => {
|
|
this.logger.error('Error: ' + JSON.stringify(err));
|
|
if (action.payload.fromDialog) {
|
|
this.handleErrorWithoutAlert('SendPayment', UI_MESSAGES.SEND_PAYMENT, 'Send Payment Failed.', err);
|
|
} else {
|
|
this.handleErrorWithAlert('SendPayment', UI_MESSAGES.SEND_PAYMENT, 'Send Payment Failed', this.CHILD_API_URL + environment.PAYMENTS_API, err);
|
|
}
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
transactionsFetch = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_TRANSACTIONS_ECL),
|
|
mergeMap((action: ECLActions.FetchTransactions) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchTransactions', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get<Transaction[]>(this.CHILD_API_URL + environment.ON_CHAIN_API + '/transactions?count=1000&skip=0');
|
|
}),
|
|
map((transactions) => {
|
|
this.logger.info(transactions);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchTransactions', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_TRANSACTIONS_ECL,
|
|
payload: (transactions && transactions.length > 0) ? transactions : []
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchTransactions', UI_MESSAGES.NO_SPINNER, 'Fetching Transactions Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
));
|
|
|
|
SendOnchainFunds = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.SEND_ONCHAIN_FUNDS_ECL),
|
|
mergeMap((action: ECLActions.SendOnchainFunds) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.SEND_FUNDS));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'SendOnchainFunds', status: APICallStatusEnum.INITIATED }));
|
|
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 ECLActions.UpdateAPICallStatus({ action: 'SendOnchainFunds', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.SEND_FUNDS));
|
|
this.store.dispatch(new ECLActions.FetchOnchainBalance());
|
|
return {
|
|
type: ECLActions.SEND_ONCHAIN_FUNDS_RES_ECL,
|
|
payload: postRes
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('SendOnchainFunds', UI_MESSAGES.SEND_FUNDS, 'Sending Fund Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
createInvoice = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.CREATE_INVOICE_ECL),
|
|
mergeMap((action: ECLActions.CreateInvoice) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.CREATE_INVOICE));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'CreateInvoice', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.post(this.CHILD_API_URL + environment.INVOICES_API, action.payload).
|
|
pipe(
|
|
map((postRes: Invoice) => {
|
|
this.logger.info(postRes);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'CreateInvoice', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.CREATE_INVOICE));
|
|
postRes.timestamp = new Date().getTime() / 1000;
|
|
postRes.expiresAt = Math.round(postRes.timestamp + action.payload.expireIn);
|
|
postRes.description = action.payload.description;
|
|
postRes.status = 'unpaid';
|
|
this.store.dispatch(new RTLActions.OpenAlert({
|
|
data: {
|
|
invoice: postRes,
|
|
newlyAdded: false,
|
|
component: ECLInvoiceInformationComponent
|
|
}
|
|
}));
|
|
return {
|
|
type: ECLActions.ADD_INVOICE_ECL,
|
|
payload: postRes
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('CreateInvoice', UI_MESSAGES.CREATE_INVOICE, 'Create Invoice Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
invoicesFetch = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.FETCH_INVOICES_ECL),
|
|
mergeMap((action: ECLActions.FetchInvoices) => {
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchInvoices', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get<Invoice[]>(this.CHILD_API_URL + environment.INVOICES_API).
|
|
pipe(
|
|
map((res: Invoice[]) => {
|
|
this.logger.info(res);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'FetchInvoices', status: APICallStatusEnum.COMPLETED }));
|
|
return {
|
|
type: ECLActions.SET_INVOICES_ECL,
|
|
payload: res
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('FetchInvoices', UI_MESSAGES.NO_SPINNER, 'Fetching Invoices Failed.', err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
peerLookup = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.PEER_LOOKUP_ECL),
|
|
mergeMap((action: ECLActions.PeerLookup) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.SEARCHING_NODE));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'Lookup', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get(this.CHILD_API_URL + environment.NETWORK_API + '/nodes/' + action.payload).
|
|
pipe(
|
|
map((resPeer) => {
|
|
this.logger.info(resPeer);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'Lookup', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.SEARCHING_NODE));
|
|
return {
|
|
type: ECLActions.SET_LOOKUP_ECL,
|
|
payload: resPeer
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithAlert('Lookup', UI_MESSAGES.SEARCHING_NODE, 'Peer Lookup Failed', this.CHILD_API_URL + environment.NETWORK_API + '/nodes/' + action.payload, err);
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
invoiceLookup = createEffect(() => this.actions.pipe(
|
|
ofType(ECLActions.INVOICE_LOOKUP_ECL),
|
|
mergeMap((action: ECLActions.InvoiceLookup) => {
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.SEARCHING_INVOICE));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'Lookup', status: APICallStatusEnum.INITIATED }));
|
|
return this.httpClient.get(this.CHILD_API_URL + environment.INVOICES_API + '/' + action.payload).
|
|
pipe(
|
|
map((resInvoice) => {
|
|
this.logger.info(resInvoice);
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: 'Lookup', status: APICallStatusEnum.COMPLETED }));
|
|
this.store.dispatch(new RTLActions.CloseSpinner(UI_MESSAGES.SEARCHING_INVOICE));
|
|
this.store.dispatch(new ECLActions.UpdateInvoice(resInvoice));
|
|
return {
|
|
type: ECLActions.SET_LOOKUP_ECL,
|
|
payload: resInvoice
|
|
};
|
|
}),
|
|
catchError((err: any) => {
|
|
this.handleErrorWithoutAlert('Lookup', UI_MESSAGES.SEARCHING_INVOICE, 'Invoice Lookup Failed', err);
|
|
this.store.dispatch(new RTLActions.OpenSnackBar({ message: 'Invoice Refresh Failed.', type: 'ERROR' }));
|
|
return of({ type: RTLActions.VOID });
|
|
})
|
|
);
|
|
})
|
|
));
|
|
|
|
setLookup = createEffect(
|
|
() => this.actions.pipe(
|
|
ofType(ECLActions.SET_LOOKUP_ECL),
|
|
map((action: ECLActions.SetLookup) => {
|
|
this.logger.info(action.payload);
|
|
return action.payload;
|
|
})),
|
|
{ dispatch: false }
|
|
);
|
|
|
|
initializeRemainingData(info: any, landingPage: string) {
|
|
this.sessionService.setItem('eclUnlocked', 'true');
|
|
const node_data = {
|
|
identity_pubkey: info.nodeId,
|
|
alias: info.alias,
|
|
testnet: info.network === 'testnet',
|
|
chains: info.publicAddresses,
|
|
uris: info.uris,
|
|
version: info.version,
|
|
numberOfPendingChannels: 0
|
|
};
|
|
this.store.dispatch(new RTLActions.OpenSpinner(UI_MESSAGES.INITALIZE_NODE_DATA));
|
|
this.store.dispatch(new RTLActions.SetNodeData(node_data));
|
|
this.store.dispatch(new ECLActions.FetchInvoices());
|
|
this.store.dispatch(new ECLActions.FetchChannels({ fetchPayments: true }));
|
|
this.store.dispatch(new ECLActions.FetchFees());
|
|
this.store.dispatch(new ECLActions.FetchOnchainBalance());
|
|
this.store.dispatch(new ECLActions.FetchPeers());
|
|
let newRoute = this.location.path();
|
|
if (newRoute.includes('/lnd/')) {
|
|
newRoute = newRoute.replace('/lnd/', '/ecl/');
|
|
} else if (newRoute.includes('/cl/')) {
|
|
newRoute = newRoute.replace('/cl/', '/ecl/');
|
|
}
|
|
if (newRoute.includes('/login') || newRoute.includes('/error') || newRoute === '' || landingPage === 'HOME' || newRoute.includes('?access-key=')) {
|
|
newRoute = '/ecl/home';
|
|
}
|
|
this.router.navigate([newRoute]);
|
|
}
|
|
|
|
handleErrorWithoutAlert(actionName: string, uiMessage: 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(uiMessage));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: actionName, status: APICallStatusEnum.ERROR, statusCode: err.status.toString(), message: this.commonService.extractErrorMessage(err, genericErrorMessage) }));
|
|
}
|
|
}
|
|
|
|
handleErrorWithAlert(actionName: string, uiMessage: 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(uiMessage));
|
|
const errMsg = this.commonService.extractErrorMessage(err);
|
|
this.store.dispatch(new RTLActions.OpenAlert({
|
|
data: {
|
|
type: 'ERROR',
|
|
alertTitle: alertTitle,
|
|
message: { code: err.status, message: errMsg, URL: errURL },
|
|
component: ErrorMessageComponent
|
|
}
|
|
}));
|
|
this.store.dispatch(new ECLActions.UpdateAPICallStatus({ action: actionName, status: APICallStatusEnum.ERROR, statusCode: err.status.toString(), message: errMsg, URL: errURL }));
|
|
}
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this.unSubs.forEach((completeSub) => {
|
|
completeSub.next(null);
|
|
completeSub.complete();
|
|
});
|
|
}
|
|
|
|
}
|