import isUrl from "is-url" ; import orderBy from "lodash/orderBy" ; import { Observable } from "rxjs/Observable" ; import semver from "semver" ; import { PeerApiResponse } from "@/utils/ark-client" ; import { HttpClient } from "@/utils/ark-http-client" ; export class PeerDiscovery { private version : string | undefined ; private latency : number | undefined ; private orderBy : string [ ] = [ "latency" , "desc" ] ; private readonly seeds : PeerApiResponse [ ] ; private readonly httpClient : HttpClient ; constructor ( httpClient , seeds = [ ] ) { this . seeds = seeds ; this . httpClient = httpClient ; } public find ( { networkOrHost , defaultPort = 4003 , } ) { return new Observable ( ( observer ) => { if ( ! networkOrHost || typeof networkOrHost !== "string" ) { observer . error ( "No network or host provided" ) ; } try { if ( isUrl ( networkOrHost ) ) { this . getSeedsFromHost ( networkOrHost , defaultPort ) . subscribe ( ( response ) => { observer . next ( new PeerDiscovery ( this . httpClient , response ) ) ; observer . complete ( ) ; } , ( e ) => observer . error ( e ) ) ; } else { this . getSeedsFromRepository ( networkOrHost , defaultPort ) . subscribe ( ( response ) => { observer . next ( new PeerDiscovery ( this . httpClient , response ) ) ; observer . complete ( ) ; } , ( e ) => observer . error ( e ) ) ; } } catch ( error ) { observer . error ( "Failed to discovery any peers." ) ; } } ) ; } public getSeeds ( ) { return this . seeds ; } public withVersion ( version ) { this . version = version ; return this ; } public withLatency ( latency ) { this . latency = latency ; return this ; } public sortBy ( key , direction = "desc" ) { this . orderBy = [ key , direction ] ; return this ; } public findPeers ( opts = { } ) { return new Observable ( ( observer ) => { if ( ! opts . retry ) { opts . retry = { retries : 0 } ; } if ( ! opts . timeout ) { opts . timeout = 3000 ; } if ( this . seeds . length ) { const seed = this . seeds [ Math . floor ( Math . random ( ) * this . seeds . length ) ] ; this . httpClient . get ( `http://${ seed . ip }:${ seed . port }/api/peers` , { headers : { "API-Version" : "2" , } , } ) . subscribe ( ( body ) => { let peers = body . data ; if ( this . version ) { peers = peers . filter ( ( peer ) => semver . satisfies ( peer . version , this . version ) ) ; } if ( this . latency ) { peers = peers . filter ( ( peer ) => peer . latency <= this . latency ) ; } observer . next ( orderBy < PeerApiResponse > ( peers , [ this . orderBy [ 0 ] ] , [ this . orderBy [ 1 ] as any ] ) ) ; observer . complete ( ) ; } , ( e ) => { observer . error ( e ) ; } ) ; } else { observer . next ( [ ] ) ; observer . complete ( ) ; } } ) ; } public findPeersWithPlugin ( name , opts = { } ) { return new Observable ( ( observer ) => { this . findPeers ( opts ) . subscribe ( ( response ) => { const peers = [ ] ; for ( const peer of response ) { const pluginName = Object . keys ( peer . ports ) . find ( ( key ) => key . split ( "/" ) [ 1 ] === name ) ; if ( pluginName ) { const port = peer . ports [ pluginName ] ; if ( port >= 1 && port <= 65535 ) { const peerData = { ip : peer . ip , port , } ; if ( opts . additional && Array . isArray ( opts . additional ) ) { for ( const additional of opts . additional ) { if ( typeof peer [ additional ] === "undefined" ) { continue ; } peerData [ additional ] = peer [ additional ] ; } } peers . push ( peerData ) ; } } } observer . next ( peers ) ; observer . complete ( ) ; } , ( e ) => observer . error ( e ) ) ; } ) ; } private getSeedsFromHost ( host , defaultPort ) { return new Observable ( ( observer ) => { this . httpClient . get ( host ) . subscribe ( ( body ) => { const seeds = [ ] ; for ( const seed of body . data ) { let port = defaultPort ; if ( seed . ports ) { const walletApiPort = seed . ports [ "@arkecosystem/core-wallet-api" ] ; const apiPort = seed . ports [ "@arkecosystem/core-api" ] ; if ( walletApiPort >= 1 && walletApiPort <= 65535 ) { port = walletApiPort ; } else if ( apiPort >= 1 && apiPort <= 65535 ) { port = apiPort ; } } seeds . push ( { ip : seed . ip , port } ) ; } observer . next ( seeds ) ; observer . complete ( ) ; } , ( e ) => observer . error ( e ) ) ; } ) ; } private getSeedsFromRepository ( network , defaultPort ) { return new Observable ( ( observer ) => { this . httpClient . get ( `https://raw.githubusercontent.com/ArkEcosystem/peers/master/${ network }.json` ) . subscribe ( ( body ) => { const seeds = [ ] ; for ( const seed of body ) { seeds . push ( { ip : seed . ip , port : defaultPort } ) ; } observer . next ( seeds ) ; observer . complete ( ) ; } , ( e ) => observer . error ( e ) ) ; } ) ; } }