@leofcoin/peernet 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/coverage/lcov-report/base.css +224 -0
  2. package/coverage/lcov-report/block-navigation.js +87 -0
  3. package/coverage/lcov-report/codec-format-interface.js.html +637 -0
  4. package/coverage/lcov-report/dht-response.js.html +193 -0
  5. package/coverage/lcov-report/favicon.png +0 -0
  6. package/coverage/lcov-report/index.html +131 -0
  7. package/coverage/lcov-report/prettify.css +1 -0
  8. package/coverage/lcov-report/prettify.js +2 -0
  9. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  10. package/coverage/lcov-report/sorter.js +196 -0
  11. package/coverage/lcov.info +199 -0
  12. package/dist/browser/peernet.js +1288 -1385
  13. package/dist/commonjs/{client-bd0caeb7.js → client-1a1f75e6.js} +41 -41
  14. package/dist/commonjs/{codec-4a768e5e.js → codec-8c8c652f.js} +118 -118
  15. package/dist/commonjs/codec-format-interface.js +167 -167
  16. package/dist/commonjs/codec.js +2 -2
  17. package/dist/commonjs/dht-response.js +11 -11
  18. package/dist/commonjs/dht.js +2 -2
  19. package/dist/commonjs/hash.js +149 -149
  20. package/dist/commonjs/{http-2b0735ef.js → http-7bbac90a.js} +1 -1
  21. package/dist/commonjs/peernet-message.js +2 -2
  22. package/dist/commonjs/peernet.js +843 -934
  23. package/dist/commonjs/request.js +2 -2
  24. package/dist/commonjs/response.js +2 -2
  25. package/dist/module/peernet.js +1357 -1448
  26. package/package.json +2 -18
  27. package/src/client.js +75 -75
  28. package/src/codec/codec-format-interface.js +172 -172
  29. package/src/codec/codec.js +124 -124
  30. package/src/dht/dht.js +121 -121
  31. package/src/discovery/peer-discovery.js +75 -75
  32. package/src/hash/hash.js +155 -155
  33. package/src/http/client/http-client.js +44 -44
  34. package/src/messages/dht-response.js +14 -14
  35. package/src/peer.js +67 -67
  36. package/src/peernet.js +612 -612
  37. package/src/proto/chat-message.proto.js +7 -7
  38. package/src/utils/utils.js +78 -78
package/src/peernet.js CHANGED
@@ -1,612 +1,612 @@
1
- import Client from './../node_modules/@leofcoin/peernet-swarm/dist/es/client.js'
2
- import LeofcoinStorage from '@leofcoin/storage'
3
- import LeofcoinStorageClient from './http/client/storage.js'
4
- import PeernetMessage from './messages/peernet-message.js'
5
- import DHTMessage from './messages/dht.js'
6
- import DHTMessageResponse from './messages/dht-response.js'
7
- import DataMessage from './messages/data.js'
8
- import PsMessage from './messages/ps.js'
9
- import PeerMessage from './messages/peer.js'
10
- import RequestMessage from './messages/request.js'
11
- import ResponseMessage from './messages/response.js'
12
- import PeerMessageResponse from './messages/peer-response.js'
13
- import DataMessageResponse from './messages/data-response.js'
14
- import ChatMessage from './messages/chat-message.js'
15
- import PeerDiscovery from './discovery/peer-discovery'
16
- import DHT from './dht/dht.js'
17
- import Hash from './hash/hash'
18
- import codecs from './codec/codecs'
19
- import { debug, protoFor, target } from './utils/utils.js'
20
- import generateAccount from '@leofcoin/generate-account'
21
- import MessageHandler from './handlers/message.js'
22
- import { encapsulatedError, dhtError,
23
- nothingFoundError } from './errors/errors.js'
24
-
25
- globalThis.leofcoin = globalThis.leofcoin || {}
26
- globalThis.globalSub = globalThis.globalSub || new PubSub({verbose: true})
27
-
28
- /**
29
- * @access public
30
- * @example
31
- * const peernet = new Peernet();
32
- */
33
- export default class Peernet {
34
- /**
35
- * @access public
36
- * @param {Object} options
37
- * @param {String} options.network - desired network
38
- * @param {String} options.root - path to root directory
39
- * @param {String} options.storePrefix - prefix for datatores (lfc)
40
- *
41
- * @return {Promise} instance of Peernet
42
- *
43
- * @example
44
- * const peernet = new Peernet({network: 'leofcoin', root: '.leofcoin'});
45
- */
46
- constructor(options = {}) {
47
- this._discovered = []
48
- /**
49
- * @property {String} network - current network
50
- */
51
- this.network = options.network || 'leofcoin'
52
- const parts = this.network.split(':')
53
-
54
- if (!options.storePrefix) options.storePrefix = 'lfc'
55
- if (!options.port) options.port = 2000
56
- if (!options.root) {
57
- if (parts[1]) options.root = `.${parts[0]}/peernet/${parts[1]}`
58
- else options.root = `.${this.network}/peernet`
59
- }
60
- globalThis.peernet = this
61
- this.bw = {
62
- up: 0,
63
- down: 0,
64
- }
65
- return this._init(options)
66
- }
67
-
68
- get defaultStores() {
69
- return ['account', 'wallet', 'block', 'transaction', 'chain', 'data', 'message']
70
- }
71
-
72
- addProto(name, proto) {
73
- if (!this.protos[name]) this.protos[name] = proto
74
- }
75
-
76
- addCodec(name, codec) {
77
- if (!this.codecs[name]) this.codecs[name] = codec
78
- }
79
-
80
- async addStore(name, prefix, root, isPrivate = true) {
81
- if (name === 'block' || name === 'transaction' || name === 'chain' ||
82
- name === 'data' || name === 'message') isPrivate = false
83
-
84
- let Storage
85
- if (this.hasDaemon) {
86
- Storage = LeofcoinStorageClient
87
- } else {
88
- Storage = LeofcoinStorage
89
- }
90
- globalThis[`${name}Store`] = globalThis[`${name}Store`] ||
91
- await new Storage(`${prefix}-${name}`, root)
92
-
93
- globalThis[`${name}Store`].private = isPrivate
94
- if (!isPrivate) this.stores.push(name)
95
- }
96
-
97
-
98
- /**
99
- * @see MessageHandler
100
- */
101
- prepareMessage(to, data) {
102
- return this._messageHandler.prepareMessage(this.id, to, data)
103
- }
104
-
105
- /**
106
- * @access public
107
- *
108
- * @return {Array} peerId
109
- */
110
- get peers() {
111
- return [...connections.values()]
112
- }
113
-
114
- /**
115
- * @private
116
- *
117
- * @param {Object} options
118
- * @param {String} options.root - path to root directory
119
- *
120
- * @return {Promise} instance of Peernet
121
- */
122
- async _init(options) {
123
- // peernetDHT aka closesPeer by coordinates
124
- /**
125
- * @type {Object}
126
- * @property {Object} peer Instance of Peer
127
- */
128
- this.dht = new DHT()
129
- /**
130
- * @type {Map}
131
- * @property {Object} peer Instance of Peer
132
- */
133
- this.peerMap = new Map()
134
- this.stores = []
135
- this.requestProtos = {}
136
- this.storePrefix = options.storePrefix
137
- this.root = options.root
138
-
139
- /**
140
- * proto Object containing protos
141
- * @type {Object}
142
- * @property {PeernetMessage} protos[peernet-message] messageNode
143
- * @property {DHTMessage} protos[peernet-dht] messageNode
144
- * @property {DHTMessageResponse} protos[peernet-dht-response] messageNode
145
- * @property {DataMessage} protos[peernet-data] messageNode
146
- * @property {DataMessageResponse} protos[peernet-data-response] messageNode
147
- */
148
- globalThis.peernet.protos = {
149
- 'peernet-request': RequestMessage,
150
- 'peernet-response': ResponseMessage,
151
- 'peernet-peer': PeerMessage,
152
- 'peernet-peer-response': PeerMessageResponse,
153
- 'peernet-message': PeernetMessage,
154
- 'peernet-dht': DHTMessage,
155
- 'peernet-dht-response': DHTMessageResponse,
156
- 'peernet-data': DataMessage,
157
- 'peernet-data-response': DataMessageResponse,
158
- 'peernet-ps': PsMessage,
159
- 'chat-message': ChatMessage,
160
- }
161
-
162
- this.protos = globalThis.peernet.protos
163
- this.codecs = codecs
164
-
165
- this._messageHandler = new MessageHandler(this.network)
166
-
167
- const {daemon, environment} = await target()
168
- this.hasDaemon = daemon
169
-
170
- HTTP_IMPORT
171
-
172
- for (const store of this.defaultStores) {
173
- await this.addStore(store, options.storePrefix, options.root)
174
- }
175
-
176
- try {
177
- const pub = await accountStore.get('public')
178
- this.id = pub.walletId
179
- } catch (e) {
180
- if (e.code === 'ERR_NOT_FOUND') {
181
- const wallet = {}
182
- const {identity, accounts, config} = await generateAccount(this.network)
183
- wallet.identity = identity
184
- wallet.accounts = accounts
185
- wallet.version = 1
186
- walletStore.put(wallet)
187
- await accountStore.put('config', config);
188
- await accountStore.put('public', {walletId: wallet.identity.walletId});
189
-
190
- this.id = wallet.identity.walletId
191
- } else {
192
- throw e
193
- }
194
- }
195
- this._peerHandler = new PeerDiscovery(this.id)
196
- this.peerId = this.id
197
-
198
- pubsub.subscribe('peer:connected', async (peer) => {
199
- console.log(peer);
200
- // console.log({connected: peer.id, as: this._getPeerId(peer.id) });
201
- // peer.on('peernet.data', async (message) => {
202
- // const id = message.id
203
- // message = new PeernetMessage(Buffer.from(message.data.data))
204
- // const proto = protoFor(message.decoded.data)
205
- // this._protoHandler({id, proto}, peer)
206
- // })
207
- })
208
-
209
- pubsub.subscribe('peer:data', async message => {
210
- if (!message.data) return
211
- const {id, data} = JSON.parse(new TextDecoder().decode(message.data))
212
- const uint8Array = new Uint8Array(Object.keys(data).length)
213
- for (var i = 0; i < Object.keys(data).length; i++) {
214
- uint8Array[i] = data[i]
215
- }
216
- message = new PeernetMessage(uint8Array)
217
- const proto = protoFor(message.decoded.data)
218
-
219
- const from = new TextDecoder().decode(message.decoded.from)
220
- this._protoHandler({id, proto}, this.client.connections[from], from)
221
- })
222
-
223
- /**
224
- * @access public
225
- * @type {PeernetClient}
226
- */
227
- this.client = new Client(this.id)
228
- if (globalThis.onbeforeunload) {
229
- globalThis.addEventListener('beforeunload', async () => this.client.close());
230
- }
231
- return this
232
- }
233
-
234
- _getPeerId(id) {
235
- for (const entry of [...this.peerMap.entries()]) {
236
- for (const _id of entry[1]) {
237
- if (_id === id) return entry[0]
238
- }
239
- }
240
- }
241
-
242
- addRequestHandler(name, method) {
243
- this.requestProtos[name] = method
244
- }
245
-
246
- /**
247
- * @private
248
- *
249
- * @param {Buffer} message - peernet message
250
- * @param {PeernetPeer} peer - peernet peer
251
- */
252
- async _protoHandler(message, peer, from) {
253
- const {id, proto} = message
254
- this.bw.down += proto.encoded.length
255
-
256
- if (proto.name === 'peernet-dht') {
257
- let { hash, store } = proto.decoded
258
- let has;
259
-
260
- if (!store) {
261
- has = await this.has(hash)
262
- } else {
263
- store = globalThis[`${store}Store`]
264
- if (store.private) has = false
265
- else has = await store.has(hash)
266
- }
267
- const data = new DHTMessageResponse({hash, has})
268
- const node = await this.prepareMessage(from, data.encoded)
269
-
270
- peer.send(Buffer.from(JSON.stringify({id, data: node.encoded})))
271
- this.bw.up += node.encoded.length
272
- } else if (proto.name === 'peernet-data') {
273
- let { hash, store } = proto.decoded
274
- let data
275
- if (!store) {
276
- store = await this.whichStore([...this.stores], hash)
277
- } else {
278
- store = globalThis[`${store}Store`]
279
- }
280
- if (store && !store.private) {
281
- data = await store.get(hash)
282
-
283
- if (data) {
284
- data = new DataMessageResponse({hash, data: data.decoded ? Buffer.from(JSON.stringify(data)) : Buffer.from(data)});
285
-
286
- const node = await this.prepareMessage(from, data.encoded)
287
- peer.send(Buffer.from(JSON.stringify({id, data: node.encoded})))
288
- this.bw.up += node.encoded.length
289
- }
290
- } else {
291
- // ban (trying to access private st)
292
- }
293
-
294
- } else if (proto.name === 'peernet-request') {
295
- // TODO: make dynamic
296
- // exposeddevapi[proto.decoded.request](proto.decoded.params)
297
- const method = this.requestProtos[proto.decoded.request]
298
- if (method) {
299
- const data = await method()
300
- const node = await this.prepareMessage(from, data.encoded)
301
- peer.send(new TextEncoder().encode(JSON.stringify({id, data: node.encoded})))
302
- this.bw.up += node.encoded.length
303
- }
304
- } else if (proto.name === 'peernet-ps' &&
305
- this._getPeerId(peer.id) !== this.id.toString()) {
306
- globalSub.publish(proto.decoded.topic.toString(), proto.decoded.data.toString())
307
- }
308
- // }
309
- }
310
-
311
- /**
312
- * performs a walk and resolves first encounter
313
- *
314
- * @param {String} hash
315
- */
316
- async walk(hash) {
317
- if (!hash) throw new Error('hash expected, received undefined')
318
- const data = new DHTMessage({hash})
319
- const clientId = this.client.id
320
- for (const peer of this.peers) {
321
- const node = await this.prepareMessage(peer.id, data.encoded)
322
-
323
- const result = await peer.request(node.encoded)
324
-
325
- let proto = protoFor(result.data)
326
-
327
- if (proto.name !== 'peernet-message') throw encapsulatedError()
328
- const from = proto.decoded.from
329
- proto = protoFor(proto.decoded.data)
330
-
331
- if (proto.name !== 'peernet-dht-response') throw dhtError(proto.name)
332
-
333
- // TODO: give ip and port (just used for location)
334
- if (!peer.connection.remoteAddress || !peer.connection.localAddress) {
335
- peer.connection.remoteFamily = 'ipv4'
336
- peer.connection.remoteAddress = '127.0.0.1'
337
- peer.connection.remotePort = '0000'
338
- }
339
-
340
- const peerInfo = {
341
- family: peer.connection.remoteFamily || peer.connection.localFamily,
342
- address: peer.connection.remoteAddress || peer.connection.localAddress,
343
- port: peer.connection.remotePort || peer.connection.localPort,
344
- id: from,
345
- }
346
-
347
- if (proto.decoded.has) this.dht.addProvider(peerInfo, proto.decoded.hash)
348
- }
349
- return
350
- }
351
-
352
- /**
353
- * Override DHT behavior, try's finding the content three times
354
- *
355
- * @param {String} hash
356
- */
357
- async providersFor(hash) {
358
- let providers = await this.dht.providersFor(hash)
359
- // walk the network to find a provider
360
- if (!providers || providers.length === 0) {
361
- await this.walk(hash)
362
- providers = await this.dht.providersFor(hash)
363
- // second walk the network to find a provider
364
- if (!providers || providers.length === 0) {
365
- await this.walk(hash)
366
- providers = await this.dht.providersFor(hash)
367
- }
368
- // last walk
369
- if (!providers || providers.length === 0) {
370
- await this.walk(hash)
371
- providers = await this.dht.providersFor(hash)
372
- }
373
- }
374
- // undefined if no providers given
375
- return providers
376
- }
377
-
378
- get block() {
379
- return {
380
- get: async (hash) => {
381
- const data = await blockStore.has(hash)
382
- if (data) return await blockStore.get(hash)
383
- return this.requestData(hash, 'block')
384
- },
385
- put: async (hash, data) => {
386
- if (await blockStore.has(hash)) return
387
- return await blockStore.put(hash, data)
388
- },
389
- has: async (hash) => await blockStore.has(hash, 'block'),
390
- }
391
- }
392
-
393
- get transaction() {
394
- return {
395
- get: async (hash) => {
396
- const data = await transactionStore.has(hash)
397
- if (data) return await transactionStore.get(hash)
398
- return this.requestData(hash, 'transaction')
399
- },
400
- put: async (hash, data) => {
401
- if (await transactionStore.has(hash)) return
402
- return await transactionStore.put(hash, data)
403
- },
404
- has: async (hash) => await transactionStore.has(hash),
405
- }
406
- }
407
-
408
- async requestData(hash, store) {
409
- const providers = await this.providersFor(hash)
410
- if (!providers || providers.size === 0) throw nothingFoundError(hash)
411
- debug(`found ${providers.size} provider(s) for ${hash}`)
412
- // get closest peer on earth
413
- const closestPeer = await this.dht.closestPeer(providers)
414
- // get peer instance by id
415
- if (!closestPeer || !closestPeer.id) return this.requestData(hash, store.name ? store.name : store)
416
-
417
- const id = closestPeer.id.toString()
418
- if (this.peers) {
419
- let closest = this.peers.filter((peer) => {
420
- if (this._getPeerId(peer.id) === id) return peer
421
- })
422
-
423
- let data = new DataMessage({hash, store: store.name ? store.name : store});
424
-
425
- const node = await this.prepareMessage(id, data.encoded)
426
- if (closest[0]) data = await closest[0].request(node.encoded)
427
- else {
428
- closest = this.peers.filter((peer) => {
429
- if (peer.id.toString() === id) return peer
430
- })
431
- if (closest[0]) data = await closest[0].request(node.encoded)
432
- }
433
- if (data.data) {
434
- let proto = protoFor(Buffer.from(data.data))
435
- proto = protoFor(proto.decoded.data)
436
- return proto.decoded.data
437
- }
438
-
439
- // this.put(hash, proto.decoded.data)
440
- }
441
- return null
442
- }
443
-
444
-
445
- get message() {
446
- return {
447
- /**
448
- * Get content for given message hash
449
- *
450
- * @param {String} hash
451
- */
452
- get: async (hash) => {
453
- debug(`get message ${hash}`)
454
- const message = await messageStore.has(hash)
455
- if (message) return await messageStore.get(hash)
456
- return this.requestData(hash, 'message')
457
- },
458
- /**
459
- * put message content
460
- *
461
- * @param {String} hash
462
- * @param {Buffer} message
463
- */
464
- put: async (hash, message) => await messageStore.put(hash, message),
465
- /**
466
- * @param {String} hash
467
- * @return {Boolean}
468
- */
469
- has: async (hash) => await messageStore.has(hash),
470
- }
471
- }
472
-
473
- get data() {
474
- return {
475
- /**
476
- * Get content for given data hash
477
- *
478
- * @param {String} hash
479
- */
480
- get: async (hash) => {
481
- debug(`get data ${hash}`)
482
- const data = await dataStore.has(hash)
483
- if (data) return await dataStore.get(hash)
484
- return this.requestData(hash, 'data')
485
- },
486
- /**
487
- * put data content
488
- *
489
- * @param {String} hash
490
- * @param {Buffer} data
491
- */
492
- put: async (hash, data) => await dataStore.put(hash, data),
493
- /**
494
- * @param {String} hash
495
- * @return {Boolean}
496
- */
497
- has: async (hash) => await dataStore.has(hash),
498
- }
499
- }
500
-
501
- /**
502
- * goes trough given stores and tries to find data for given hash
503
- * @param {Array} stores
504
- * @param {string} hash
505
- */
506
- async whichStore(stores, hash) {
507
- let store = stores.pop()
508
- const name = store
509
- store = globalThis[`${store}Store`]
510
- if (store) {
511
- const has = await store.has(hash)
512
- if (has) return store
513
- if (stores.length > 0) return this.whichStore(stores, hash)
514
- } else return null
515
- }
516
-
517
- /**
518
- * Get content for given hash
519
- *
520
- * @param {String} hash - the hash of the wanted data
521
- * @param {String} store - storeName to access
522
- */
523
- async get(hash, store) {
524
- debug(`get ${hash}`)
525
- let data
526
- if (store) store = globalThis[`${store}Store`]
527
- if (!store) store = await this.whichStore([...this.stores], hash)
528
- if (store && await store.has(hash)) data = await store.get(hash)
529
- if (data) return data
530
-
531
- return this.requestData(hash, store.name ? store.name : store)
532
- }
533
-
534
- /**
535
- * put content
536
- *
537
- * @param {String} hash
538
- * @param {Buffer} data
539
- * @param {String} store - storeName to access
540
- */
541
- async put(hash, data, store = 'data') {
542
- store = globalThis[`${store}Store`]
543
- return store.put(hash, data)
544
- }
545
-
546
- /**
547
- * @param {String} hash
548
- * @return {Boolean}
549
- */
550
- async has(hash) {
551
- const store = await this.whichStore([...this.stores], hash)
552
- if (store) {
553
- if (store.private) return false
554
- else return true
555
- }
556
- return false
557
- }
558
-
559
- /**
560
- *
561
- * @param {String} topic
562
- * @param {String|Object|Array|Boolean|Buffer} data
563
- */
564
- async publish(topic, data) {
565
- // globalSub.publish(topic, data)
566
- if (!Buffer.isBuffer(topic)) topic = Buffer.from(topic)
567
- if (!Buffer.isBuffer(data)) data = Buffer.from(data)
568
- const id = Math.random().toString(36).slice(-12)
569
- data = new PsMessage({data, topic})
570
- for (const peer of this.peers) {
571
- if (peer.connection._connected) {
572
- if (peer.id.toString() !== this.peerId.toString()) {
573
- const node = await this.prepareMessage(peer.id, data.encoded)
574
- peer.send(Buffer.from(JSON.stringify({id, data: node.encoded})))
575
- }
576
- } else {
577
- this.removePeer(peer)
578
- }
579
- // TODO: if peer subscribed
580
- }
581
- }
582
-
583
- createHash(data, name) {
584
- return new Hash(data, {name})
585
- }
586
-
587
- /**
588
- *
589
- * @param {String} topic
590
- * @param {Method} cb
591
- */
592
- async subscribe(topic, cb) {
593
- // TODO: if peer subscribed
594
- globalSub.subscribe(topic, cb)
595
- }
596
-
597
- async removePeer(peer) {
598
- connections.delete(peer.id)
599
- }
600
-
601
- get Buffer() {
602
- return Buffer
603
- }
604
- // async block(index) {
605
- // const _values = []
606
- // for (const peer of this.peers) {
607
- // const value = await peer.request({type: 'block', index})
608
- // console.log(value);
609
- // }
610
- //
611
- // }
612
- }
1
+ import Client from './../node_modules/@leofcoin/peernet-swarm/dist/es/client.js'
2
+ import LeofcoinStorage from '@leofcoin/storage'
3
+ import LeofcoinStorageClient from './http/client/storage.js'
4
+ import PeernetMessage from './messages/peernet-message.js'
5
+ import DHTMessage from './messages/dht.js'
6
+ import DHTMessageResponse from './messages/dht-response.js'
7
+ import DataMessage from './messages/data.js'
8
+ import PsMessage from './messages/ps.js'
9
+ import PeerMessage from './messages/peer.js'
10
+ import RequestMessage from './messages/request.js'
11
+ import ResponseMessage from './messages/response.js'
12
+ import PeerMessageResponse from './messages/peer-response.js'
13
+ import DataMessageResponse from './messages/data-response.js'
14
+ import ChatMessage from './messages/chat-message.js'
15
+ import PeerDiscovery from './discovery/peer-discovery'
16
+ import DHT from './dht/dht.js'
17
+ import Hash from './hash/hash'
18
+ import codecs from './codec/codecs'
19
+ import { debug, protoFor, target } from './utils/utils.js'
20
+ import generateAccount from '@leofcoin/generate-account'
21
+ import MessageHandler from './handlers/message.js'
22
+ import { encapsulatedError, dhtError,
23
+ nothingFoundError } from './errors/errors.js'
24
+
25
+ globalThis.leofcoin = globalThis.leofcoin || {}
26
+ globalThis.globalSub = globalThis.globalSub || new PubSub({verbose: true})
27
+
28
+ /**
29
+ * @access public
30
+ * @example
31
+ * const peernet = new Peernet();
32
+ */
33
+ export default class Peernet {
34
+ /**
35
+ * @access public
36
+ * @param {Object} options
37
+ * @param {String} options.network - desired network
38
+ * @param {String} options.root - path to root directory
39
+ * @param {String} options.storePrefix - prefix for datatores (lfc)
40
+ *
41
+ * @return {Promise} instance of Peernet
42
+ *
43
+ * @example
44
+ * const peernet = new Peernet({network: 'leofcoin', root: '.leofcoin'});
45
+ */
46
+ constructor(options = {}) {
47
+ this._discovered = []
48
+ /**
49
+ * @property {String} network - current network
50
+ */
51
+ this.network = options.network || 'leofcoin'
52
+ const parts = this.network.split(':')
53
+
54
+ if (!options.storePrefix) options.storePrefix = 'lfc'
55
+ if (!options.port) options.port = 2000
56
+ if (!options.root) {
57
+ if (parts[1]) options.root = `.${parts[0]}/peernet/${parts[1]}`
58
+ else options.root = `.${this.network}/peernet`
59
+ }
60
+ globalThis.peernet = this
61
+ this.bw = {
62
+ up: 0,
63
+ down: 0,
64
+ }
65
+ return this._init(options)
66
+ }
67
+
68
+ get defaultStores() {
69
+ return ['account', 'wallet', 'block', 'transaction', 'chain', 'data', 'message']
70
+ }
71
+
72
+ addProto(name, proto) {
73
+ if (!this.protos[name]) this.protos[name] = proto
74
+ }
75
+
76
+ addCodec(name, codec) {
77
+ if (!this.codecs[name]) this.codecs[name] = codec
78
+ }
79
+
80
+ async addStore(name, prefix, root, isPrivate = true) {
81
+ if (name === 'block' || name === 'transaction' || name === 'chain' ||
82
+ name === 'data' || name === 'message') isPrivate = false
83
+
84
+ let Storage
85
+ if (this.hasDaemon) {
86
+ Storage = LeofcoinStorageClient
87
+ } else {
88
+ Storage = LeofcoinStorage
89
+ }
90
+ globalThis[`${name}Store`] = globalThis[`${name}Store`] ||
91
+ await new Storage(`${prefix}-${name}`, root)
92
+
93
+ globalThis[`${name}Store`].private = isPrivate
94
+ if (!isPrivate) this.stores.push(name)
95
+ }
96
+
97
+
98
+ /**
99
+ * @see MessageHandler
100
+ */
101
+ prepareMessage(to, data) {
102
+ return this._messageHandler.prepareMessage(this.id, to, data)
103
+ }
104
+
105
+ /**
106
+ * @access public
107
+ *
108
+ * @return {Array} peerId
109
+ */
110
+ get peers() {
111
+ return [...connections.values()]
112
+ }
113
+
114
+ /**
115
+ * @private
116
+ *
117
+ * @param {Object} options
118
+ * @param {String} options.root - path to root directory
119
+ *
120
+ * @return {Promise} instance of Peernet
121
+ */
122
+ async _init(options) {
123
+ // peernetDHT aka closesPeer by coordinates
124
+ /**
125
+ * @type {Object}
126
+ * @property {Object} peer Instance of Peer
127
+ */
128
+ this.dht = new DHT()
129
+ /**
130
+ * @type {Map}
131
+ * @property {Object} peer Instance of Peer
132
+ */
133
+ this.peerMap = new Map()
134
+ this.stores = []
135
+ this.requestProtos = {}
136
+ this.storePrefix = options.storePrefix
137
+ this.root = options.root
138
+
139
+ /**
140
+ * proto Object containing protos
141
+ * @type {Object}
142
+ * @property {PeernetMessage} protos[peernet-message] messageNode
143
+ * @property {DHTMessage} protos[peernet-dht] messageNode
144
+ * @property {DHTMessageResponse} protos[peernet-dht-response] messageNode
145
+ * @property {DataMessage} protos[peernet-data] messageNode
146
+ * @property {DataMessageResponse} protos[peernet-data-response] messageNode
147
+ */
148
+ globalThis.peernet.protos = {
149
+ 'peernet-request': RequestMessage,
150
+ 'peernet-response': ResponseMessage,
151
+ 'peernet-peer': PeerMessage,
152
+ 'peernet-peer-response': PeerMessageResponse,
153
+ 'peernet-message': PeernetMessage,
154
+ 'peernet-dht': DHTMessage,
155
+ 'peernet-dht-response': DHTMessageResponse,
156
+ 'peernet-data': DataMessage,
157
+ 'peernet-data-response': DataMessageResponse,
158
+ 'peernet-ps': PsMessage,
159
+ 'chat-message': ChatMessage,
160
+ }
161
+
162
+ this.protos = globalThis.peernet.protos
163
+ this.codecs = codecs
164
+
165
+ this._messageHandler = new MessageHandler(this.network)
166
+
167
+ const {daemon, environment} = await target()
168
+ this.hasDaemon = daemon
169
+
170
+ HTTP_IMPORT
171
+
172
+ for (const store of this.defaultStores) {
173
+ await this.addStore(store, options.storePrefix, options.root)
174
+ }
175
+
176
+ try {
177
+ const pub = await accountStore.get('public')
178
+ this.id = pub.walletId
179
+ } catch (e) {
180
+ if (e.code === 'ERR_NOT_FOUND') {
181
+ const wallet = {}
182
+ const {identity, accounts, config} = await generateAccount(this.network)
183
+ wallet.identity = identity
184
+ wallet.accounts = accounts
185
+ wallet.version = 1
186
+ walletStore.put(wallet)
187
+ await accountStore.put('config', config);
188
+ await accountStore.put('public', {walletId: wallet.identity.walletId});
189
+
190
+ this.id = wallet.identity.walletId
191
+ } else {
192
+ throw e
193
+ }
194
+ }
195
+ this._peerHandler = new PeerDiscovery(this.id)
196
+ this.peerId = this.id
197
+
198
+ pubsub.subscribe('peer:connected', async (peer) => {
199
+ console.log(peer);
200
+ // console.log({connected: peer.id, as: this._getPeerId(peer.id) });
201
+ // peer.on('peernet.data', async (message) => {
202
+ // const id = message.id
203
+ // message = new PeernetMessage(Buffer.from(message.data.data))
204
+ // const proto = protoFor(message.decoded.data)
205
+ // this._protoHandler({id, proto}, peer)
206
+ // })
207
+ })
208
+
209
+ pubsub.subscribe('peer:data', async message => {
210
+ if (!message.data) return
211
+ const {id, data} = JSON.parse(new TextDecoder().decode(message.data))
212
+ const uint8Array = new Uint8Array(Object.keys(data).length)
213
+ for (var i = 0; i < Object.keys(data).length; i++) {
214
+ uint8Array[i] = data[i]
215
+ }
216
+ message = new PeernetMessage(uint8Array)
217
+ const proto = protoFor(message.decoded.data)
218
+
219
+ const from = new TextDecoder().decode(message.decoded.from)
220
+ this._protoHandler({id, proto}, this.client.connections[from], from)
221
+ })
222
+
223
+ /**
224
+ * @access public
225
+ * @type {PeernetClient}
226
+ */
227
+ this.client = new Client(this.id)
228
+ if (globalThis.onbeforeunload) {
229
+ globalThis.addEventListener('beforeunload', async () => this.client.close());
230
+ }
231
+ return this
232
+ }
233
+
234
+ _getPeerId(id) {
235
+ for (const entry of [...this.peerMap.entries()]) {
236
+ for (const _id of entry[1]) {
237
+ if (_id === id) return entry[0]
238
+ }
239
+ }
240
+ }
241
+
242
+ addRequestHandler(name, method) {
243
+ this.requestProtos[name] = method
244
+ }
245
+
246
+ /**
247
+ * @private
248
+ *
249
+ * @param {Buffer} message - peernet message
250
+ * @param {PeernetPeer} peer - peernet peer
251
+ */
252
+ async _protoHandler(message, peer, from) {
253
+ const {id, proto} = message
254
+ this.bw.down += proto.encoded.length
255
+
256
+ if (proto.name === 'peernet-dht') {
257
+ let { hash, store } = proto.decoded
258
+ let has;
259
+
260
+ if (!store) {
261
+ has = await this.has(hash)
262
+ } else {
263
+ store = globalThis[`${store}Store`]
264
+ if (store.private) has = false
265
+ else has = await store.has(hash)
266
+ }
267
+ const data = new DHTMessageResponse({hash, has})
268
+ const node = await this.prepareMessage(from, data.encoded)
269
+
270
+ peer.send(Buffer.from(JSON.stringify({id, data: node.encoded})))
271
+ this.bw.up += node.encoded.length
272
+ } else if (proto.name === 'peernet-data') {
273
+ let { hash, store } = proto.decoded
274
+ let data
275
+ if (!store) {
276
+ store = await this.whichStore([...this.stores], hash)
277
+ } else {
278
+ store = globalThis[`${store}Store`]
279
+ }
280
+ if (store && !store.private) {
281
+ data = await store.get(hash)
282
+
283
+ if (data) {
284
+ data = new DataMessageResponse({hash, data: data.decoded ? Buffer.from(JSON.stringify(data)) : Buffer.from(data)});
285
+
286
+ const node = await this.prepareMessage(from, data.encoded)
287
+ peer.send(Buffer.from(JSON.stringify({id, data: node.encoded})))
288
+ this.bw.up += node.encoded.length
289
+ }
290
+ } else {
291
+ // ban (trying to access private st)
292
+ }
293
+
294
+ } else if (proto.name === 'peernet-request') {
295
+ // TODO: make dynamic
296
+ // exposeddevapi[proto.decoded.request](proto.decoded.params)
297
+ const method = this.requestProtos[proto.decoded.request]
298
+ if (method) {
299
+ const data = await method()
300
+ const node = await this.prepareMessage(from, data.encoded)
301
+ peer.send(new TextEncoder().encode(JSON.stringify({id, data: node.encoded})))
302
+ this.bw.up += node.encoded.length
303
+ }
304
+ } else if (proto.name === 'peernet-ps' &&
305
+ this._getPeerId(peer.id) !== this.id.toString()) {
306
+ globalSub.publish(proto.decoded.topic.toString(), proto.decoded.data.toString())
307
+ }
308
+ // }
309
+ }
310
+
311
+ /**
312
+ * performs a walk and resolves first encounter
313
+ *
314
+ * @param {String} hash
315
+ */
316
+ async walk(hash) {
317
+ if (!hash) throw new Error('hash expected, received undefined')
318
+ const data = new DHTMessage({hash})
319
+ const clientId = this.client.id
320
+ for (const peer of this.peers) {
321
+ const node = await this.prepareMessage(peer.id, data.encoded)
322
+
323
+ const result = await peer.request(node.encoded)
324
+
325
+ let proto = protoFor(result.data)
326
+
327
+ if (proto.name !== 'peernet-message') throw encapsulatedError()
328
+ const from = proto.decoded.from
329
+ proto = protoFor(proto.decoded.data)
330
+
331
+ if (proto.name !== 'peernet-dht-response') throw dhtError(proto.name)
332
+
333
+ // TODO: give ip and port (just used for location)
334
+ if (!peer.connection.remoteAddress || !peer.connection.localAddress) {
335
+ peer.connection.remoteFamily = 'ipv4'
336
+ peer.connection.remoteAddress = '127.0.0.1'
337
+ peer.connection.remotePort = '0000'
338
+ }
339
+
340
+ const peerInfo = {
341
+ family: peer.connection.remoteFamily || peer.connection.localFamily,
342
+ address: peer.connection.remoteAddress || peer.connection.localAddress,
343
+ port: peer.connection.remotePort || peer.connection.localPort,
344
+ id: from,
345
+ }
346
+
347
+ if (proto.decoded.has) this.dht.addProvider(peerInfo, proto.decoded.hash)
348
+ }
349
+ return
350
+ }
351
+
352
+ /**
353
+ * Override DHT behavior, try's finding the content three times
354
+ *
355
+ * @param {String} hash
356
+ */
357
+ async providersFor(hash) {
358
+ let providers = await this.dht.providersFor(hash)
359
+ // walk the network to find a provider
360
+ if (!providers || providers.length === 0) {
361
+ await this.walk(hash)
362
+ providers = await this.dht.providersFor(hash)
363
+ // second walk the network to find a provider
364
+ if (!providers || providers.length === 0) {
365
+ await this.walk(hash)
366
+ providers = await this.dht.providersFor(hash)
367
+ }
368
+ // last walk
369
+ if (!providers || providers.length === 0) {
370
+ await this.walk(hash)
371
+ providers = await this.dht.providersFor(hash)
372
+ }
373
+ }
374
+ // undefined if no providers given
375
+ return providers
376
+ }
377
+
378
+ get block() {
379
+ return {
380
+ get: async (hash) => {
381
+ const data = await blockStore.has(hash)
382
+ if (data) return await blockStore.get(hash)
383
+ return this.requestData(hash, 'block')
384
+ },
385
+ put: async (hash, data) => {
386
+ if (await blockStore.has(hash)) return
387
+ return await blockStore.put(hash, data)
388
+ },
389
+ has: async (hash) => await blockStore.has(hash, 'block'),
390
+ }
391
+ }
392
+
393
+ get transaction() {
394
+ return {
395
+ get: async (hash) => {
396
+ const data = await transactionStore.has(hash)
397
+ if (data) return await transactionStore.get(hash)
398
+ return this.requestData(hash, 'transaction')
399
+ },
400
+ put: async (hash, data) => {
401
+ if (await transactionStore.has(hash)) return
402
+ return await transactionStore.put(hash, data)
403
+ },
404
+ has: async (hash) => await transactionStore.has(hash),
405
+ }
406
+ }
407
+
408
+ async requestData(hash, store) {
409
+ const providers = await this.providersFor(hash)
410
+ if (!providers || providers.size === 0) throw nothingFoundError(hash)
411
+ debug(`found ${providers.size} provider(s) for ${hash}`)
412
+ // get closest peer on earth
413
+ const closestPeer = await this.dht.closestPeer(providers)
414
+ // get peer instance by id
415
+ if (!closestPeer || !closestPeer.id) return this.requestData(hash, store.name ? store.name : store)
416
+
417
+ const id = closestPeer.id.toString()
418
+ if (this.peers) {
419
+ let closest = this.peers.filter((peer) => {
420
+ if (this._getPeerId(peer.id) === id) return peer
421
+ })
422
+
423
+ let data = new DataMessage({hash, store: store.name ? store.name : store});
424
+
425
+ const node = await this.prepareMessage(id, data.encoded)
426
+ if (closest[0]) data = await closest[0].request(node.encoded)
427
+ else {
428
+ closest = this.peers.filter((peer) => {
429
+ if (peer.id.toString() === id) return peer
430
+ })
431
+ if (closest[0]) data = await closest[0].request(node.encoded)
432
+ }
433
+ if (data.data) {
434
+ let proto = protoFor(Buffer.from(data.data))
435
+ proto = protoFor(proto.decoded.data)
436
+ return proto.decoded.data
437
+ }
438
+
439
+ // this.put(hash, proto.decoded.data)
440
+ }
441
+ return null
442
+ }
443
+
444
+
445
+ get message() {
446
+ return {
447
+ /**
448
+ * Get content for given message hash
449
+ *
450
+ * @param {String} hash
451
+ */
452
+ get: async (hash) => {
453
+ debug(`get message ${hash}`)
454
+ const message = await messageStore.has(hash)
455
+ if (message) return await messageStore.get(hash)
456
+ return this.requestData(hash, 'message')
457
+ },
458
+ /**
459
+ * put message content
460
+ *
461
+ * @param {String} hash
462
+ * @param {Buffer} message
463
+ */
464
+ put: async (hash, message) => await messageStore.put(hash, message),
465
+ /**
466
+ * @param {String} hash
467
+ * @return {Boolean}
468
+ */
469
+ has: async (hash) => await messageStore.has(hash),
470
+ }
471
+ }
472
+
473
+ get data() {
474
+ return {
475
+ /**
476
+ * Get content for given data hash
477
+ *
478
+ * @param {String} hash
479
+ */
480
+ get: async (hash) => {
481
+ debug(`get data ${hash}`)
482
+ const data = await dataStore.has(hash)
483
+ if (data) return await dataStore.get(hash)
484
+ return this.requestData(hash, 'data')
485
+ },
486
+ /**
487
+ * put data content
488
+ *
489
+ * @param {String} hash
490
+ * @param {Buffer} data
491
+ */
492
+ put: async (hash, data) => await dataStore.put(hash, data),
493
+ /**
494
+ * @param {String} hash
495
+ * @return {Boolean}
496
+ */
497
+ has: async (hash) => await dataStore.has(hash),
498
+ }
499
+ }
500
+
501
+ /**
502
+ * goes trough given stores and tries to find data for given hash
503
+ * @param {Array} stores
504
+ * @param {string} hash
505
+ */
506
+ async whichStore(stores, hash) {
507
+ let store = stores.pop()
508
+ const name = store
509
+ store = globalThis[`${store}Store`]
510
+ if (store) {
511
+ const has = await store.has(hash)
512
+ if (has) return store
513
+ if (stores.length > 0) return this.whichStore(stores, hash)
514
+ } else return null
515
+ }
516
+
517
+ /**
518
+ * Get content for given hash
519
+ *
520
+ * @param {String} hash - the hash of the wanted data
521
+ * @param {String} store - storeName to access
522
+ */
523
+ async get(hash, store) {
524
+ debug(`get ${hash}`)
525
+ let data
526
+ if (store) store = globalThis[`${store}Store`]
527
+ if (!store) store = await this.whichStore([...this.stores], hash)
528
+ if (store && await store.has(hash)) data = await store.get(hash)
529
+ if (data) return data
530
+
531
+ return this.requestData(hash, store.name ? store.name : store)
532
+ }
533
+
534
+ /**
535
+ * put content
536
+ *
537
+ * @param {String} hash
538
+ * @param {Buffer} data
539
+ * @param {String} store - storeName to access
540
+ */
541
+ async put(hash, data, store = 'data') {
542
+ store = globalThis[`${store}Store`]
543
+ return store.put(hash, data)
544
+ }
545
+
546
+ /**
547
+ * @param {String} hash
548
+ * @return {Boolean}
549
+ */
550
+ async has(hash) {
551
+ const store = await this.whichStore([...this.stores], hash)
552
+ if (store) {
553
+ if (store.private) return false
554
+ else return true
555
+ }
556
+ return false
557
+ }
558
+
559
+ /**
560
+ *
561
+ * @param {String} topic
562
+ * @param {String|Object|Array|Boolean|Buffer} data
563
+ */
564
+ async publish(topic, data) {
565
+ // globalSub.publish(topic, data)
566
+ if (!Buffer.isBuffer(topic)) topic = Buffer.from(topic)
567
+ if (!Buffer.isBuffer(data)) data = Buffer.from(data)
568
+ const id = Math.random().toString(36).slice(-12)
569
+ data = new PsMessage({data, topic})
570
+ for (const peer of this.peers) {
571
+ if (peer.connection._connected) {
572
+ if (peer.id.toString() !== this.peerId.toString()) {
573
+ const node = await this.prepareMessage(peer.id, data.encoded)
574
+ peer.send(Buffer.from(JSON.stringify({id, data: node.encoded})))
575
+ }
576
+ } else {
577
+ this.removePeer(peer)
578
+ }
579
+ // TODO: if peer subscribed
580
+ }
581
+ }
582
+
583
+ createHash(data, name) {
584
+ return new Hash(data, {name})
585
+ }
586
+
587
+ /**
588
+ *
589
+ * @param {String} topic
590
+ * @param {Method} cb
591
+ */
592
+ async subscribe(topic, cb) {
593
+ // TODO: if peer subscribed
594
+ globalSub.subscribe(topic, cb)
595
+ }
596
+
597
+ async removePeer(peer) {
598
+ connections.delete(peer.id)
599
+ }
600
+
601
+ get Buffer() {
602
+ return Buffer
603
+ }
604
+ // async block(index) {
605
+ // const _values = []
606
+ // for (const peer of this.peers) {
607
+ // const value = await peer.request({type: 'block', index})
608
+ // console.log(value);
609
+ // }
610
+ //
611
+ // }
612
+ }