1 1
import ky from "ky-universal";
2 1
import isUrl from "is-url-superb";
3 1
import orderBy from "lodash.orderby";
4 1
import semver from "semver";
5
import { IPeer, IPeerResponse } from "./interfaces";
6

7 1
export class PeerDiscovery {
8
	private version: string | undefined;
9
	private latency: number | undefined;
10 1
	private orderBy: string[] = ["latency", "desc"];
11

12 1
	private constructor(private readonly seeds: IPeer[]) {}
13

14
	public static async new({
15
		networkOrHost,
16 1
		defaultPort = 4003,
17
	}: {
18
		networkOrHost: string;
19
		defaultPort?: number;
20
	}): Promise<PeerDiscovery> {
21 1
		if (!networkOrHost || typeof networkOrHost !== "string") {
22 1
			throw new Error("No network or host provided");
23
		}
24

25 1
		const seeds: IPeer[] = [];
26

27 1
		try {
28 1
			if (isUrl(networkOrHost)) {
29 1
				const body: any = await ky.get(networkOrHost).json();
30

31 1
				for (const seed of body.data) {
32 1
					let port = defaultPort;
33 1
					if (seed.ports) {
34 1
						const walletApiPort = seed.ports["@arkecosystem/core-wallet-api"];
35 1
						const apiPort = seed.ports["@arkecosystem/core-api"];
36 1
						if (walletApiPort >= 1 && walletApiPort <= 65535) {
37 1
							port = walletApiPort;
38 1
						} else if (apiPort >= 1 && apiPort <= 65535) {
39 1
							port = apiPort;
40
						}
41
					}
42

43 1
					seeds.push({ ip: seed.ip, port });
44
				}
45
			} else {
46 1
				const body: any = await ky.get(
47
					`https://raw.githubusercontent.com/ArkEcosystem/peers/master/${networkOrHost}.json`,
48
				).json();
49

50 1
				for (const seed of body) {
51 1
					seeds.push({ ip: seed.ip, port: defaultPort });
52
				}
53
			}
54
		} catch (error) {
55 1
			throw new Error("Failed to discovery any peers.");
56
		}
57

58 1
		if (!seeds.length) {
59 1
			throw new Error("No seeds found");
60
		}
61

62 1
		return new PeerDiscovery(seeds);
63
	}
64

65
	public getSeeds(): IPeer[] {
66 1
		return this.seeds;
67
	}
68

69
	public withVersion(version: string): PeerDiscovery {
70 1
		this.version = version;
71

72 1
		return this;
73
	}
74

75
	public withLatency(latency: number): PeerDiscovery {
76 1
		this.latency = latency;
77

78 1
		return this;
79
	}
80

81 1
	public sortBy(key: string, direction = "desc"): PeerDiscovery {
82 1
		this.orderBy = [key, direction];
83

84 1
		return this;
85
	}
86

87 1
	public async findPeers(opts: any = {}): Promise<IPeerResponse[]> {
88 1
		if (!opts.retry) {
89 1
			opts.retry = { limit: 0 };
90
		}
91

92 1
		if (!opts.timeout) {
93 1
			opts.timeout = 3000;
94
		}
95

96 1
		const seed: IPeer = this.seeds[Math.floor(Math.random() * this.seeds.length)];
97

98 1
		const body: any = await ky(`http://${seed.ip}:${seed.port}/api/peers`, {
99
			...opts,
100
			...{
101
				headers: {
102
					"Content-Type": "application/json",
103
				},
104
			},
105
		}).json();
106

107 1
		let peers: IPeerResponse[] = body.data;
108

109 1
		if (this.version) {
110 1
			peers = peers.filter((peer: IPeerResponse) => semver.satisfies(peer.version, this.version));
111
		}
112

113 1
		if (this.latency) {
114 1
			peers = peers.filter((peer: IPeerResponse) => peer.latency <= this.latency);
115
		}
116

117 1
		return orderBy(peers, [this.orderBy[0]], [this.orderBy[1] as any]);
118
	}
119

120 1
	public async findPeersWithPlugin(name: string, opts: { additional?: string[] } = {}): Promise<IPeer[]> {
121 1
		const peers: IPeer[] = [];
122

123 1
		for (const peer of await this.findPeers(opts)) {
124 1
			const pluginName: string | undefined = Object.keys(peer.ports).find(
125 1
				(key: string) => key.split("/")[1] === name,
126
			);
127

128 1
			if (pluginName) {
129 1
				const port: number = peer.ports[pluginName];
130

131 1
				if (port >= 1 && port <= 65535) {
132 1
					const peerData: IPeer = {
133
						ip: peer.ip,
134
						port,
135
					};
136

137 1
					if (opts.additional && Array.isArray(opts.additional)) {
138 1
						for (const additional of opts.additional) {
139 1
							if (typeof peer[additional] === "undefined") {
140 1
								continue;
141
							}
142

143 1
							peerData[additional] = peer[additional];
144
						}
145
					}
146

147 1
					peers.push(peerData);
148
				}
149
			}
150
		}
151

152 1
		return peers;
153
	}
154

155 1
	public async findPeersWithoutEstimates(opts: { additional?: string[] } = {}): Promise<IPeer[]> {
156 1
		const apiPeers: IPeer[] = await this.findPeersWithPlugin('core-api', opts);
157

158 1
		const requests = apiPeers.map(peer => {
159 1
			return ky.get(`http://${peer.ip}:${peer.port}/api/blocks?limit=1`).json();
160
		});
161

162 1
		const responses = await Promise.all(requests);
163

164 1
		const peers: IPeer[] = [];
165

166 1
		for (const i in responses) {
167 1
			if (!(responses[i] as any).meta.totalCountIsEstimate) {
168 1
				peers.push(apiPeers[i]);
169
			}
170
		}
171

172 1
		return peers;
173
	}
174
}

Read our documentation on viewing source code .

Loading