git ssb

16+

Dominic / patchbay



Tree: de80e702316a3f736dcb1f65f3840baf28f51ab9

Files: de80e702316a3f736dcb1f65f3840baf28f51ab9 / modules_extra / network.js

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

Built with git-ssb-web