Files: 62404afd51be20fc4b7925c7ad3b91a4a4b34915 / modules_extra / network.js
6257 bytesRaw
1 | const fs = require('fs') |
2 | // const { isVisible } = require('is-visible') |
3 | const h = require('../h') |
4 | const human = require('human-time') |
5 | |
6 | const Struct = require('@mmckegg/mutant/struct') |
7 | const Value = require('@mmckegg/mutant/value') |
8 | const Dict = require('@mmckegg/mutant/dict') |
9 | const toCollection = require('@mmckegg/mutant/dict-to-collection') |
10 | const mutantMap = require('@mmckegg/mutant/map') |
11 | const when = require('@mmckegg/mutant/when') |
12 | const computed = require('@mmckegg/mutant/computed') |
13 | |
14 | //var avatar = plugs.first(exports.avatar = []) |
15 | //var sbot_gossip_peers = plugs.first(exports.sbot_gossip_peers = []) |
16 | //var sbot_gossip_connect = plugs.first(exports.sbot_gossip_connect = []) |
17 | |
18 | exports.needs = { |
19 | avatar_image_link: 'first', |
20 | avatar_name_link: 'first', |
21 | sbot_gossip_peers: 'first', |
22 | sbot_gossip_connect: 'first' |
23 | } |
24 | |
25 | exports.gives = { |
26 | menu_items: true, |
27 | builtin_tabs: true, |
28 | screen_view: true, |
29 | mcss: true |
30 | } |
31 | |
32 | //sbot_gossip_connect |
33 | //sbot_gossip_add |
34 | |
35 | |
36 | function legacyToMultiServer(addr) { |
37 | return 'net:'+addr.host + ':'+addr.port + '~shs:'+addr.key.substring(1).replace('.ed25519','') |
38 | } |
39 | |
40 | //on the same wifi network |
41 | function isLocal (peer) { |
42 | // don't rely on private ip address, because |
43 | // cjdns creates fake private ip addresses. |
44 | return ip.isPrivate(peer.host) && peer.type === 'local' |
45 | } |
46 | |
47 | |
48 | function getType (peer) { |
49 | return ( |
50 | isLongterm(peer) ? 'modern' |
51 | : isLegacy(peer) ? 'legacy' |
52 | : isInactive(peer) ? 'inactive' |
53 | : isUnattempted(peer) ? 'unattempted' |
54 | : 'other' //should never happen |
55 | ) |
56 | |
57 | //pub is running scuttlebot >=8 |
58 | //have connected successfully. |
59 | function isLongterm (peer) { |
60 | return peer.ping && peer.ping.rtt && peer.ping.rtt.mean > 0 |
61 | } |
62 | |
63 | //pub is running scuttlebot < 8 |
64 | //have connected sucessfully |
65 | function isLegacy (peer) { |
66 | return /connect/.test(peer.state) || (peer.duration && peer.duration.mean) > 0 && !isLongterm(peer) |
67 | } |
68 | |
69 | //tried to connect, but failed. |
70 | function isInactive (peer) { |
71 | return peer.stateChange && (peer.duration && peer.duration.mean == 0) |
72 | } |
73 | |
74 | //havn't tried to connect peer yet. |
75 | function isUnattempted (peer) { |
76 | return !peer.stateChange |
77 | } |
78 | } |
79 | |
80 | function origin (peer) { |
81 | return peer.source === 'local' ? 0 : 1 |
82 | } |
83 | |
84 | function round(n) { |
85 | return Math.round(n*100)/100 |
86 | } |
87 | |
88 | function duration (s) { |
89 | if(!s) return s |
90 | if (Math.abs(s) > 30000) |
91 | return round(s/60000)+'m' |
92 | else if (Math.abs(s) > 500) |
93 | return round(s/1000)+'s' |
94 | else |
95 | return round(s)+'ms' |
96 | } |
97 | |
98 | function peerListSort (a, b) { |
99 | var states = { |
100 | connected: 3, |
101 | connecting: 2 |
102 | } |
103 | |
104 | //types of peers |
105 | var types = { |
106 | modern: 4, |
107 | legacy: 3, |
108 | inactive: 2, |
109 | unattempted: 1, |
110 | other: 0 |
111 | } |
112 | |
113 | return ( |
114 | (states[b.state] || 0) - (states[a.state] || 0) |
115 | || origin(b) - origin(a) |
116 | || types[getType(b)] - types[getType(a)] |
117 | || b.stateChange - a.stateChange |
118 | ) |
119 | } |
120 | |
121 | function formatDate (time) { |
122 | return new Date(time).toString() |
123 | } |
124 | |
125 | function humanDate (time) { |
126 | return human(new Date(time)).replace(/minute/, 'min').replace(/second/, 'sec') |
127 | } |
128 | |
129 | exports.create = function (api) { |
130 | |
131 | return { |
132 | menu_items: () => h('a', {href: '#/network'}, '/network'), |
133 | builtin_tabs: () => ['/network'], |
134 | screen_view, |
135 | mcss: () => fs.readFileSync(__filename.replace(/js$/, 'mcss'), 'utf8') |
136 | } |
137 | |
138 | function screen_view (path) { |
139 | if (path !== '/network') return |
140 | |
141 | var peers = obs_gossip_peers(api) |
142 | |
143 | return h('div', { style: {'overflow':'auto'}, className: 'column scroller' }, [ |
144 | h('Network', [ |
145 | mutantMap(peers, peer => { |
146 | var { key, ping, source, state, stateChange } = peer |
147 | |
148 | return h('NetworkConnection', [ |
149 | h('section.avatar', [ |
150 | api.avatar_image_link(key()), |
151 | ]), |
152 | h('section.name', [ |
153 | api.avatar_name_link(key()), |
154 | ]), |
155 | h('section.type', [ |
156 | computed(peer, getType), |
157 | ]), |
158 | h('section.source', [ |
159 | h('label', 'source:'), |
160 | h('code', source) |
161 | ]), |
162 | h('section.state', [ |
163 | h('label', 'state:'), |
164 | h('i', { |
165 | className: computed(state, (state) => '-'+state) |
166 | }), |
167 | h('code', when(state, state, 'not connected')) |
168 | ]), |
169 | h('section.actions', [ |
170 | h('button', { |
171 | 'ev-click': () => { |
172 | api.sbot_gossip_connect(peer(), (err) => { |
173 | if(err) console.error(err) |
174 | else console.log('connected to', peer()) |
175 | }) |
176 | }}, |
177 | 'connect' |
178 | ) |
179 | ]), |
180 | h('section.time-ago', [ |
181 | h('div', |
182 | { title: computed(stateChange, formatDate) }, |
183 | [ computed(stateChange, humanDate) ] |
184 | ) |
185 | ]), |
186 | h('section.ping', [ |
187 | h('div.rtt', [ |
188 | h('label', 'rtt:'), |
189 | h('code', computed(ping.rtt.mean, duration)) |
190 | ]), |
191 | h('div.skew', [ |
192 | h('label', 'skew:'), |
193 | h('code', computed(ping.skew.mean, duration)) |
194 | ]), |
195 | ]), |
196 | h('section.address', [ |
197 | h('code', computed(peer, legacyToMultiServer)) |
198 | ]) |
199 | ]) |
200 | }) |
201 | ]) |
202 | ]) |
203 | } |
204 | } |
205 | |
206 | function obs_gossip_peers (api) { |
207 | var timer = null |
208 | var state = Dict({}, { |
209 | onListen: () => { |
210 | timer = setInterval(refresh, 5e3) |
211 | }, |
212 | onUnlisten: () => { |
213 | clearInterval(timer) |
214 | } |
215 | }) |
216 | |
217 | refresh() |
218 | |
219 | return toCollection.values(state) |
220 | |
221 | function refresh () { |
222 | api.sbot_gossip_peers((err, peers) => { |
223 | peers.forEach(data => { |
224 | var current = state.get(data.key) |
225 | if (!current) { |
226 | current = Peer() |
227 | current.set(data) |
228 | state.put(data.key, current) |
229 | } else { |
230 | current.set(data) |
231 | } |
232 | }) |
233 | }) |
234 | } |
235 | } |
236 | |
237 | function Peer () { |
238 | var peer = Struct({ |
239 | key: Value(), |
240 | ping: Struct({ |
241 | rtt: Struct({ |
242 | mean: Value() |
243 | }), |
244 | skew: Struct({ |
245 | mean: Value() |
246 | }) |
247 | }), |
248 | source: Value(), |
249 | state: Value(), |
250 | stateChange: Value() |
251 | }) |
252 | |
253 | return peer |
254 | } |
255 | |
256 |
Built with git-ssb-web