2023-04-01 11:40:04 +09:00
import { ChangeDetectionStrategy , Component , OnInit , ChangeDetectorRef } from '@angular/core' ;
2022-04-29 03:57:27 +04:00
import { ActivatedRoute , ParamMap } from '@angular/router' ;
2023-04-01 11:40:04 +09:00
import { Observable , of , EMPTY } from 'rxjs' ;
import { catchError , map , switchMap , tap , share } from 'rxjs/operators' ;
2022-09-21 17:23:45 +02:00
import { SeoService } from '../../services/seo.service' ;
2022-11-15 11:04:33 -06:00
import { ApiService } from '../../services/api.service' ;
2022-04-29 03:57:27 +04:00
import { LightningApiService } from '../lightning-api.service' ;
2022-09-21 17:23:45 +02:00
import { GeolocationData } from '../../shared/components/geolocation/geolocation.component' ;
2022-11-04 23:24:44 -06:00
import { ILiquidityAd , parseLiquidityAdHex } from './liquidity-ad' ;
2022-11-24 12:19:19 +09:00
import { haversineDistance , kmToMiles } from '../../../app/shared/common.utils' ;
2022-11-04 23:24:44 -06:00
interface CustomRecord {
type : string ;
payload : string ;
}
2022-04-29 03:57:27 +04:00
@Component ( {
selector : 'app-node' ,
templateUrl : './node.component.html' ,
2022-05-01 03:01:27 +04:00
styleUrls : [ './node.component.scss' ] ,
changeDetection : ChangeDetectionStrategy.OnPush ,
2022-04-29 03:57:27 +04:00
} )
export class NodeComponent implements OnInit {
node$ : Observable < any > ;
2022-05-05 23:19:24 +04:00
statistics$ : Observable < any > ;
2022-05-01 03:01:27 +04:00
publicKey$ : Observable < string > ;
2022-05-06 00:20:14 +04:00
selectedSocketIndex = 0 ;
2022-05-06 00:52:25 +04:00
qrCodeVisible = false ;
2022-07-24 11:51:05 +02:00
channelsListStatus : string ;
2022-07-24 12:34:50 +02:00
error : Error ;
publicKey : string ;
2022-08-29 22:25:43 +02:00
channelListLoading = false ;
2022-09-22 18:35:16 +02:00
clearnetSocketCount = 0 ;
torSocketCount = 0 ;
2022-11-04 21:59:54 -06:00
hasDetails = false ;
showDetails = false ;
2022-11-04 23:24:44 -06:00
liquidityAd : ILiquidityAd ;
tlvRecords : CustomRecord [ ] ;
2022-11-15 11:04:33 -06:00
avgChannelDistance$ : Observable < number | null > ;
2023-07-08 10:43:37 +02:00
showFeatures = false ;
2023-04-01 11:40:04 +09:00
nodeOwner$ : Observable < any > ;
2022-11-15 11:04:33 -06:00
kmToMiles = kmToMiles ;
2022-08-04 11:30:32 +02:00
2022-04-29 03:57:27 +04:00
constructor (
2022-11-15 11:04:33 -06:00
private apiService : ApiService ,
2022-04-29 03:57:27 +04:00
private lightningApiService : LightningApiService ,
private activatedRoute : ActivatedRoute ,
2022-05-15 19:22:14 +04:00
private seoService : SeoService ,
2023-04-01 11:40:04 +09:00
private cd : ChangeDetectorRef ,
2022-08-30 10:42:50 +02:00
) { }
2022-04-29 03:57:27 +04:00
ngOnInit ( ) : void {
this . node $ = this . activatedRoute . paramMap
. pipe (
switchMap ( ( params : ParamMap ) = > {
2022-07-24 12:34:50 +02:00
this . publicKey = params . get ( 'public_key' ) ;
2022-11-04 23:24:44 -06:00
this . tlvRecords = [ ] ;
this . liquidityAd = null ;
2022-05-01 03:01:27 +04:00
return this . lightningApiService . getNode $ ( params . get ( 'public_key' ) ) ;
2022-05-06 00:20:14 +04:00
} ) ,
map ( ( node ) = > {
2022-10-07 00:54:33 +04:00
this . seoService . setTitle ( $localize ` Node: ${ node . alias } ` ) ;
2023-08-30 20:26:07 +09:00
this . seoService . setDescription ( $localize ` :@@meta.description.lightning.node:Overview for the Lightning network node named ${ node . alias } . See channels, capacity, location, fee stats, and more. ` ) ;
2023-01-12 20:48:16 +04:00
this . clearnetSocketCount = 0 ;
this . torSocketCount = 0 ;
2022-05-15 19:22:14 +04:00
2022-05-06 00:20:14 +04:00
const socketsObject = [ ] ;
for ( const socket of node . sockets . split ( ',' ) ) {
2022-05-06 00:52:25 +04:00
if ( socket === '' ) {
continue ;
}
2022-05-06 00:20:14 +04:00
let label = '' ;
if ( socket . match ( /(?:[0-9]{1,3}\.){3}[0-9]{1,3}/ ) ) {
label = 'IPv4' ;
2022-09-22 18:35:16 +02:00
this . clearnetSocketCount ++ ;
2022-05-06 00:20:14 +04:00
} else if ( socket . indexOf ( '[' ) > - 1 ) {
label = 'IPv6' ;
2022-09-22 18:35:16 +02:00
this . clearnetSocketCount ++ ;
2022-05-06 00:20:14 +04:00
} else if ( socket . indexOf ( 'onion' ) > - 1 ) {
label = 'Tor' ;
2022-09-22 18:35:16 +02:00
this . torSocketCount ++ ;
2022-05-06 00:20:14 +04:00
}
socketsObject . push ( {
label : label ,
socket : node.public_key + '@' + socket ,
} ) ;
}
node . socketsObject = socketsObject ;
2022-08-04 11:30:32 +02:00
node . avgCapacity = node . capacity / Math . max ( 1 , node . active_channel_count ) ;
2022-08-18 17:14:09 +02:00
if ( ! node ? . country && ! node ? . city &&
! node ? . subdivision && ! node ? . iso ) {
node . geolocation = null ;
} else {
node . geolocation = < GeolocationData > {
country : node.country?.en ,
city : node.city?.en ,
subdivision : node.subdivision?.en ,
iso : node.iso_code ,
} ;
}
2022-05-06 00:20:14 +04:00
return node ;
} ) ,
2022-11-04 21:59:54 -06:00
tap ( ( node ) = > {
this . hasDetails = Object . keys ( node . custom_records ) . length > 0 ;
2022-11-04 23:24:44 -06:00
for ( const [ type , payload ] of Object . entries ( node . custom_records ) ) {
if ( typeof payload !== 'string' ) {
break ;
}
let parsed = false ;
if ( type === '1' ) {
const ad = parseLiquidityAdHex ( payload ) ;
if ( ad ) {
parsed = true ;
this . liquidityAd = ad ;
}
}
if ( ! parsed ) {
this . tlvRecords . push ( { type , payload } ) ;
}
}
2022-11-04 21:59:54 -06:00
} ) ,
2022-07-24 12:34:50 +02:00
catchError ( err = > {
this . error = err ;
2023-03-09 02:34:21 -06:00
this . seoService . logSoft404 ( ) ;
2022-07-24 12:34:50 +02:00
return [ {
alias : this.publicKey ,
public_key : this.publicKey ,
} ] ;
} )
2022-04-29 03:57:27 +04:00
) ;
2022-11-15 11:04:33 -06:00
this . avgChannelDistance $ = this . activatedRoute . paramMap
. pipe (
switchMap ( ( params : ParamMap ) = > {
return this . apiService . getChannelsGeo $ ( params . get ( 'public_key' ) , 'nodepage' ) ;
} ) ,
map ( ( channelsGeo ) = > {
if ( channelsGeo ? . length ) {
const totalDistance = channelsGeo . reduce ( ( sum , chan ) = > {
return sum + haversineDistance ( chan [ 3 ] , chan [ 2 ] , chan [ 7 ] , chan [ 6 ] ) ;
} , 0 ) ;
return totalDistance / channelsGeo . length ;
} else {
return null ;
}
} ) ,
catchError ( ( ) = > {
return null ;
} )
) as Observable < number | null > ;
2023-04-01 11:40:04 +09:00
this . nodeOwner $ = this . activatedRoute . paramMap
. pipe (
switchMap ( ( params : ParamMap ) = > {
return this . apiService . getNodeOwner $ ( params . get ( 'public_key' ) ) . pipe (
switchMap ( ( response ) = > {
if ( response . status === 204 ) {
return of ( false ) ;
}
return of ( response . body ) ;
} ) ,
catchError ( ( ) = > {
return of ( false ) ;
} )
)
} ) ,
share ( ) ,
) ;
2022-04-29 03:57:27 +04:00
}
2022-11-04 21:59:54 -06:00
toggleShowDetails ( ) : void {
this . showDetails = ! this . showDetails ;
}
2022-05-06 00:20:14 +04:00
changeSocket ( index : number ) {
this . selectedSocketIndex = index ;
}
2022-07-24 11:51:05 +02:00
onChannelsListStatusChanged ( e ) {
this . channelsListStatus = e ;
}
2022-08-29 22:25:43 +02:00
onLoadingEvent ( e ) {
this . channelListLoading = e ;
}
2023-07-08 10:43:37 +02:00
toggleFeatures() {
this . showFeatures = ! this . showFeatures ;
return false ;
}
2022-04-29 03:57:27 +04:00
}