@leofcoin/peernet 1.1.80 → 1.1.82

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 (71) hide show
  1. package/.esdoc.json +10 -10
  2. package/.gitattributes +2 -2
  3. package/.prettierrc +7 -7
  4. package/.travis.yml +27 -27
  5. package/BREAKING_CHANGES.md +34 -34
  6. package/LICENSE +21 -21
  7. package/README.md +72 -72
  8. package/deploy.js +8 -8
  9. package/exports/browser/{client-Depp28gl.js → client-C0VVXIWm.js} +2 -2
  10. package/exports/browser/{index-DqPlTtAJ.js → index-CEwkDK9g.js} +5 -496
  11. package/exports/browser/{messages-RYLqPGkg.js → messages-BdevLRCA.js} +164 -164
  12. package/exports/browser/{peernet-B7TZP-Wg.js → peernet-DEIKLS2i.js} +185 -185
  13. package/exports/browser/peernet.js +1 -1
  14. package/exports/{messages-CRhtDipD.js → messages-BmpgEM4y.js} +163 -163
  15. package/exports/peernet.js +184 -184
  16. package/exports/src/prompts/password.js +3 -3
  17. package/index.html +19 -19
  18. package/package.json +71 -71
  19. package/rollup.config.js +63 -63
  20. package/src/dht/dht.ts +147 -147
  21. package/src/discovery/peer-discovery.js +75 -75
  22. package/src/errors/errors.js +12 -12
  23. package/src/handlers/data.js +15 -15
  24. package/src/handlers/message.js +34 -34
  25. package/src/identity.ts +104 -104
  26. package/src/messages/chat.js +13 -13
  27. package/src/messages/data-response.js +13 -13
  28. package/src/messages/data.js +17 -17
  29. package/src/messages/dht-response.js +13 -13
  30. package/src/messages/dht.js +21 -21
  31. package/src/messages/file-link.js +17 -17
  32. package/src/messages/file.js +17 -17
  33. package/src/messages/peer-response.js +13 -13
  34. package/src/messages/peer.js +13 -13
  35. package/src/messages/peernet.js +13 -13
  36. package/src/messages/ps.js +13 -13
  37. package/src/messages/request.js +13 -13
  38. package/src/messages/response.js +13 -13
  39. package/src/messages.js +13 -13
  40. package/src/peer-info.js +9 -9
  41. package/src/peernet.ts +817 -817
  42. package/src/prompts/password/browser.js +1 -1
  43. package/src/prompts/password/node.js +6 -6
  44. package/src/proto/chat-message.proto.js +6 -6
  45. package/src/proto/data-response.proto.js +4 -4
  46. package/src/proto/data.proto.js +4 -4
  47. package/src/proto/dht-response.proto.js +4 -4
  48. package/src/proto/dht.proto.js +4 -4
  49. package/src/proto/file-link.proto.js +5 -5
  50. package/src/proto/file.proto.js +5 -5
  51. package/src/proto/peer-response.proto.js +3 -3
  52. package/src/proto/peer.proto.js +3 -3
  53. package/src/proto/peernet.proto.js +7 -7
  54. package/src/proto/ps.proto.js +4 -4
  55. package/src/proto/request.proto.js +4 -4
  56. package/src/proto/response.proto.js +3 -3
  57. package/src/types.ts +25 -25
  58. package/src/utils/utils.js +77 -77
  59. package/test/client.js +14 -14
  60. package/test/codec.js +56 -56
  61. package/test/hash.js +13 -13
  62. package/test/index.js +3 -3
  63. package/test/lastBlock.js +7 -7
  64. package/test/messages.js +26 -26
  65. package/test/peernet.js +17 -17
  66. package/test/peernet.test.js +159 -159
  67. package/test.js +62 -62
  68. package/test2.js +13 -13
  69. package/test3.js +15 -15
  70. package/test4.js +7 -7
  71. package/tsconfig.json +11 -11
package/src/peernet.ts CHANGED
@@ -1,817 +1,817 @@
1
- import '@vandeurenglenn/debug'
2
- import PubSub from '@vandeurenglenn/little-pubsub'
3
- import PeerDiscovery from './discovery/peer-discovery.js'
4
- import DHT, { DHTProvider, DHTProviderDistanceResult } from './dht/dht.js'
5
- import { BufferToUint8Array, protoFor, target } from './utils/utils.js'
6
- import MessageHandler from './handlers/message.js'
7
- import dataHandler from './handlers/data.js'
8
- import { dhtError, nothingFoundError } from './errors/errors.js'
9
- import { Storage as LeofcoinStorageClass } from '@leofcoin/storage'
10
- import { utils as codecUtils } from '@leofcoin/codecs'
11
- import Identity from './identity.js'
12
- import swarm from '@netpeer/swarm/client'
13
-
14
- globalThis.LeofcoinStorage = LeofcoinStorageClass
15
-
16
- globalThis.leofcoin = globalThis.leofcoin || {}
17
- globalThis.pubsub = globalThis.pubsub || new PubSub()
18
- globalThis.globalSub = globalThis.globalSub || new PubSub()
19
-
20
- declare global {
21
- var LeofcoinStorage: typeof LeofcoinStorageClass
22
- var peernet: Peernet
23
- var pubsub: PubSub
24
- var globalSub: PubSub
25
- var blockStore: LeofcoinStorageClass
26
- var transactionStore: LeofcoinStorageClass
27
- var messageStore: LeofcoinStorageClass
28
- var dataStore: LeofcoinStorageClass
29
- var walletStore: LeofcoinStorageClass
30
- var chainStore: LeofcoinStorageClass
31
- }
32
-
33
- const debug = globalThis.createDebugger('peernet')
34
- /**
35
- * @access public
36
- * @example
37
- * const peernet = new Peernet();
38
- */
39
- export default class Peernet {
40
- storePrefix: string
41
- root: string
42
- identity: Identity
43
- stores: string[] = []
44
- peerId: string
45
- /**
46
- * @type {Object}
47
- * @property {Object} peer Instance of Peer
48
- */
49
- dht: DHT = new DHT()
50
- /** @leofcoin/peernet-swarm/client */
51
- client: swarm
52
- network: string
53
- stars: string[]
54
- networkVersion: string
55
- bw: {
56
- up: number
57
- down: number
58
- }
59
- hasDaemon: boolean = false
60
- autoStart: boolean = true
61
- #starting: boolean = false
62
- #started: boolean = false
63
- requestProtos = {}
64
- _messageHandler: MessageHandler
65
- _peerHandler: PeerDiscovery
66
- protos: {}
67
- version
68
-
69
- /**
70
- * @access public
71
- * @param {Object} options
72
- * @param {String} options.network - desired network
73
- * @param {String} options.stars - star list for selected network (these should match, don't mix networks)
74
- * @param {String} options.root - path to root directory
75
- * @param {String} options.version - path to root directory
76
- * @param {String} options.storePrefix - prefix for datatores (lfc)
77
- *
78
- * @return {Promise} instance of Peernet
79
- *
80
- * @example
81
- * const peernet = new Peernet({network: 'leofcoin', root: '.leofcoin'});
82
- */
83
- constructor(options, password) {
84
- /**
85
- * @property {String} network - current network
86
- */
87
- this.network = options.network || 'leofcoin'
88
- this.autoStart = options.autoStart === undefined ? true : options.autoStart
89
- this.stars = options.stars
90
- this.version = options.version
91
- const parts = this.network.split(':')
92
- this.networkVersion = options.networkVersion || parts.length > 1 ? parts[1] : 'mainnet'
93
-
94
- if (!options.storePrefix) options.storePrefix = 'lfc'
95
- if (!options.port) options.port = 2000
96
- if (!options.root) {
97
- parts[1] ? (options.root = `.${parts[0]}/${parts[1]}`) : (options.root = `.${this.network}`)
98
- }
99
-
100
- globalThis.peernet = this
101
- this.bw = {
102
- up: 0,
103
- down: 0
104
- }
105
- // @ts-ignore
106
- return this._init(options, password)
107
- }
108
-
109
- get id() {
110
- return this.identity.id
111
- }
112
-
113
- get selectedAccount(): string {
114
- return this.identity.selectedAccount
115
- }
116
-
117
- get accounts(): Promise<[[name: string, externalAddress: string, internalAddress: string]]> {
118
- return this.identity.accounts
119
- }
120
-
121
- get defaultStores() {
122
- return ['account', 'wallet', 'block', 'transaction', 'chain', 'data', 'message']
123
- }
124
-
125
- selectAccount(account: string) {
126
- return this.identity.selectAccount(account)
127
- }
128
-
129
- addProto(name, proto) {
130
- if (!globalThis.peernet.protos[name]) globalThis.peernet.protos[name] = proto
131
- }
132
-
133
- addCodec(codec) {
134
- return codecUtils.addCodec(codec)
135
- }
136
-
137
- async addStore(name, prefix, root, isPrivate = true) {
138
- if (name === 'block' || name === 'transaction' || name === 'chain' || name === 'data' || name === 'message')
139
- isPrivate = false
140
-
141
- let Storage
142
-
143
- this.hasDaemon ? (Storage = LeofcoinStorageClient) : (Storage = LeofcoinStorage)
144
-
145
- if (!globalThis[`${name}Store`]) {
146
- globalThis[`${name}Store`] = new Storage(name, root)
147
- await globalThis[`${name}Store`].init()
148
- }
149
-
150
- globalThis[`${name}Store`].private = isPrivate
151
- if (!isPrivate) this.stores.push(name)
152
- }
153
-
154
- /**
155
- * @see MessageHandler
156
- */
157
- prepareMessage(data) {
158
- return this._messageHandler.prepareMessage(data)
159
- }
160
-
161
- /**
162
- * @access public
163
- *
164
- * @return {Array} peerId
165
- */
166
- get peers() {
167
- return Object.entries(this.client?.connections)
168
- }
169
-
170
- get connections() {
171
- return this.client?.connections || {}
172
- }
173
-
174
- /**
175
- * @return {String} id - peerId
176
- */
177
- getConnection(id) {
178
- return this.connections[id]
179
- }
180
-
181
- /**
182
- * @private
183
- *
184
- * @param {Object} options
185
- * @param {String} options.root - path to root directory
186
- *
187
- * @return {Promise} instance of Peernet
188
- */
189
- async _init(options: { storePrefix?: string; root?: string }, password: string): Promise<Peernet> {
190
- this.storePrefix = options.storePrefix
191
- this.root = options.root
192
-
193
- const {
194
- RequestMessage,
195
- ResponseMessage,
196
- PeerMessage,
197
- PeerMessageResponse,
198
- PeernetMessage,
199
- DHTMessage,
200
- DHTMessageResponse,
201
- DataMessage,
202
- DataMessageResponse,
203
- PsMessage,
204
- ChatMessage,
205
- PeernetFile
206
- // FolderMessageResponse
207
- } = await import(/* webpackChunkName: "messages" */ './messages.js')
208
-
209
- /**
210
- * proto Object containing protos
211
- * @type {Object}
212
- * @property {PeernetMessage} protos[peernet-message] messageNode
213
- * @property {DHTMessage} protos[peernet-dht] messageNode
214
- * @property {DHTMessageResponse} protos[peernet-dht-response] messageNode
215
- * @property {DataMessage} protos[peernet-data] messageNode
216
- * @property {DataMessageResponse} protos[peernet-data-response] messageNode
217
- */
218
-
219
- globalThis.peernet.protos = {
220
- 'peernet-request': RequestMessage,
221
- 'peernet-response': ResponseMessage,
222
- 'peernet-peer': PeerMessage,
223
- 'peernet-peer-response': PeerMessageResponse,
224
- 'peernet-message': PeernetMessage,
225
- 'peernet-dht': DHTMessage,
226
- 'peernet-dht-response': DHTMessageResponse,
227
- 'peernet-data': DataMessage,
228
- 'peernet-data-response': DataMessageResponse,
229
- 'peernet-ps': PsMessage,
230
- 'chat-message': ChatMessage,
231
- 'peernet-file': PeernetFile
232
- }
233
-
234
- this._messageHandler = new MessageHandler(this.network)
235
-
236
- const { daemon, environment } = await target()
237
- this.hasDaemon = daemon
238
-
239
- for (const store of this.defaultStores) {
240
- await this.addStore(store, options.storePrefix, options.root)
241
- }
242
-
243
- this.identity = new Identity(this.network)
244
- await this.identity.load(password)
245
-
246
- this._peerHandler = new PeerDiscovery(this.id)
247
- this.peerId = this.id
248
-
249
- // this.addRequestHandler('handshake', () => {
250
- // return new peernet.protos['peernet-response']({
251
- // response: { peerId: this.id }
252
- // })
253
- // })
254
-
255
- // pubsub.subscribe('peer:discovered', async (peer) => {
256
- // // console.log(peer);
257
-
258
- // if (this.requestProtos['version'] && !peer.version) {
259
- // let data = await new globalThis.peernet.protos['peernet-request']({
260
- // request: 'version'
261
- // })
262
- // let node = await globalThis.peernet.prepareMessage(data)
263
- // let response = await peer.request(node.encoded)
264
- // response = await new globalThis.peernet.protos['peernet-response'](new Uint8Array(Object.values(response)))
265
- // peer.version = response.decoded.response.version
266
- // }
267
-
268
- // if (!peer.peerId) {
269
- // let data = await new globalThis.peernet.protos['peernet-request']({
270
- // request: 'handshake'
271
- // })
272
- // let node = await globalThis.peernet.prepareMessage(data)
273
- // let response = await peer.request(node.encoded)
274
-
275
- // response = await new globalThis.peernet.protos['peernet-response'](new Uint8Array(Object.values(response)))
276
- // // todo: response.decoded should be the response and not response.peerId
277
- // // todo: ignore above and remove discover completly
278
- // response.decoded.response.peerId
279
- // }
280
-
281
- // this.connections[peer.peerId] = peer
282
- // pubsub.publish('peer:connected', peer)
283
- // todo: cleanup discovered
284
- // })
285
-
286
- // pubsub.subscribe('peer:left', this.#peerLeft.bind(this))
287
-
288
- /**
289
- * converts data -> message -> proto
290
- * @see DataHandler
291
- */
292
- pubsub.subscribe('peer:data', dataHandler)
293
-
294
- // // todo: remove below, already handles in the swarm
295
- // if (globalThis.navigator) {
296
- // globalThis.addEventListener('beforeunload', async () => this.client.close())
297
- // } else {
298
- // process.on('SIGTERM', async () => {
299
- // process.stdin.resume()
300
- // try {
301
- // await this.client.close()
302
- // } catch (error) {
303
- // // @ts-ignore
304
- // await this.client.close()
305
- // }
306
- // process.exit()
307
- // })
308
- // }
309
- if (this.autoStart) await this.start()
310
- return this
311
- }
312
-
313
- async start() {
314
- if (this.#starting || this.#started) return
315
-
316
- this.#starting = true
317
- const importee = await import('@netpeer/swarm/client')
318
- /**
319
- * @access public
320
- * @type {PeernetClient}
321
- */
322
- console.log(this.stars)
323
-
324
- this.client = new importee.default({
325
- peerId: this.id,
326
- networkVersion: this.networkVersion,
327
- version: this.version,
328
- stars: this.stars
329
- })
330
- this.#started = true
331
- this.#starting = false
332
- }
333
-
334
- // todo: remove, handled in swarm now
335
- // #peerLeft(peer: SwarmPeer) {
336
- // for (const [id, _peer] of Object.entries(this.connections)) {
337
- // if (_peer.id === peer.id && this.connections[id] && !this.connections[id].connected) {
338
- // delete this.connections[id]
339
- // this.removePeer(_peer)
340
- // }
341
- // }
342
- // }
343
-
344
- addRequestHandler(name, method) {
345
- this.requestProtos[name] = method
346
- }
347
-
348
- async sendMessage(peer, id, data) {
349
- if (peer.connected) {
350
- await peer.send(data, id)
351
- this.bw.up += data.length
352
- return id
353
- } else {
354
- return new Promise((resolve, reject) => {
355
- const onError = (error) => {
356
- this.removePeer(peer)
357
- reject(error)
358
- }
359
- peer.once('error', onError)
360
- peer.once('connect', async () => {
361
- await peer.send(data, id)
362
- this.bw.up += data.length
363
- peer.removeListener('error', onError)
364
- resolve(id)
365
- })
366
- })
367
- }
368
- }
369
-
370
- async handleDHT(peer, id, proto) {
371
- let { hash, store } = proto.decoded
372
- let has
373
-
374
- if (store) {
375
- store = globalThis[`${store}Store`]
376
- has = store.private ? false : await store.has(hash)
377
- } else {
378
- has = await this.has(hash)
379
- }
380
-
381
- const data = await new globalThis.peernet.protos['peernet-dht-response']({
382
- hash,
383
- has
384
- })
385
- const node = await this.prepareMessage(data)
386
-
387
- this.sendMessage(peer, id, node.encoded)
388
- }
389
-
390
- async handleData(peer, id, proto) {
391
- let { hash, store } = proto.decoded
392
- let data
393
- try {
394
- store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash))
395
-
396
- if (store && !store.private) {
397
- data = await store.get(hash)
398
-
399
- if (data) {
400
- data = await new globalThis.peernet.protos['peernet-data-response']({
401
- hash,
402
- data
403
- })
404
-
405
- const node = await this.prepareMessage(data)
406
- this.sendMessage(peer, id, node.encoded)
407
- }
408
- } else {
409
- // ban (trying to access private st)
410
- }
411
- } catch (error) {
412
- return this.requestData(hash, store)
413
- }
414
- }
415
-
416
- async handleRequest(peer, id, proto) {
417
- const method = this.requestProtos[proto.decoded.request]
418
- if (method) {
419
- const data = await method(proto.decoded.requested)
420
- const node = await this.prepareMessage(data)
421
- this.sendMessage(peer, id, node.encoded)
422
- }
423
- }
424
-
425
- /**
426
- * @private
427
- *
428
- * @param {Buffer} message - peernet message
429
- * @param {PeernetPeer} peer - peernet peer
430
- */
431
- async _protoHandler(message, peer, from) {
432
- const { id, proto } = message
433
-
434
- this.bw.down += proto.encoded.length
435
- switch (proto.name) {
436
- case 'peernet-dht': {
437
- this.handleDHT(peer, id, proto)
438
- break
439
- }
440
- case 'peernet-data': {
441
- this.handleData(peer, id, proto)
442
- break
443
- }
444
- case 'peernet-request': {
445
- this.handleRequest(peer, id, proto)
446
- break
447
- }
448
-
449
- case 'peernet-ps': {
450
- globalSub.publish(new TextDecoder().decode(proto.decoded.topic), proto.decoded.data)
451
- }
452
- }
453
- }
454
-
455
- /**
456
- * performs a walk and resolves first encounter
457
- *
458
- * @param {String} hash
459
- */
460
- async walk(hash) {
461
- if (!hash) throw new Error('hash expected, received undefined')
462
- const data = await new globalThis.peernet.protos['peernet-dht']({ hash })
463
- const walk = async (peer, peerId) => {
464
- const node = await this.prepareMessage(data)
465
- let result = await peer.request(node.encoded)
466
- result = new Uint8Array(Object.values(result))
467
- const proto = await protoFor(result)
468
- if (proto.name !== 'peernet-dht-response') throw dhtError(proto.name)
469
-
470
- const peerInfo = {
471
- ...peer.connectionStats,
472
- id: peerId
473
- }
474
-
475
- if (proto.decoded.has) this.dht.addProvider(peerInfo, proto.decoded.hash)
476
- }
477
- let walks = []
478
- for (const [peerId, peer] of Object.entries(this.connections)) {
479
- if (peerId !== this.id) {
480
- walks.push(walk(peer, peerId))
481
- }
482
- }
483
- return Promise.all(walks)
484
- }
485
-
486
- /**
487
- * Override DHT behavior, try's finding the content three times
488
- *
489
- * @param {String} hash
490
- */
491
- async providersFor(hash: string, store?: undefined) {
492
- let providers = this.dht.providersFor(hash)
493
- // walk the network to find a provider
494
- let tries = 0
495
- while ((!providers && tries < 3) || (providers && Object.keys(providers).length === 0 && tries < 3)) {
496
- tries += 1
497
- await this.walk(hash)
498
- providers = this.dht.providersFor(hash)
499
- }
500
- // undefined if no providers given
501
- return providers
502
- }
503
-
504
- get block() {
505
- return {
506
- get: async (hash: string) => {
507
- const data = await blockStore.has(hash)
508
- if (data) return blockStore.get(hash)
509
- return this.requestData(hash, 'block')
510
- },
511
- put: async (hash: string, data: Uint8Array) => {
512
- if (await blockStore.has(hash)) return
513
- return await blockStore.put(hash, data)
514
- },
515
- has: async (hash: string) => await blockStore.has(hash)
516
- }
517
- }
518
-
519
- get transaction() {
520
- return {
521
- get: async (hash: string) => {
522
- const data = await transactionStore.has(hash)
523
- if (data) return await transactionStore.get(hash)
524
- return this.requestData(hash, 'transaction')
525
- },
526
- put: async (hash: string, data: Uint8Array) => {
527
- if (await transactionStore.has(hash)) return
528
- return await transactionStore.put(hash, data)
529
- },
530
- has: async (hash: string) => await transactionStore.has(hash)
531
- }
532
- }
533
-
534
- async requestData(hash, store) {
535
- const providers = await this.providersFor(hash)
536
- if (!providers || (providers && Object.keys(providers).length === 0)) throw nothingFoundError(hash)
537
- debug(`found ${Object.keys(providers).length} provider(s) for ${hash}`)
538
- // get closest peer on earth
539
- const closestPeer: DHTProvider = Object.values(providers)[0]
540
-
541
- // get peer instance by id
542
- if (!closestPeer || !closestPeer.id) return this.requestData(hash, store?.name || store)
543
-
544
- const id = closestPeer.id
545
- const peer = this.connections[id]
546
-
547
- if (peer?.connected) {
548
- let data = await new globalThis.peernet.protos['peernet-data']({
549
- hash,
550
- store: store?.name || store
551
- })
552
-
553
- const node = await this.prepareMessage(data)
554
-
555
- if (peer) data = await peer.request(node.encoded)
556
- else {
557
- // fallback and try every provider found
558
- const promises = []
559
- const providers = await this.providersFor(hash, store)
560
- for (const provider of Object.values(providers)) {
561
- const peer = this.connections[provider.id]
562
-
563
- if (peer) promises.push(peer.request(node.encoded))
564
- }
565
- data = await Promise.race(promises)
566
- }
567
- if (data) data = new Uint8Array(Object.values(data))
568
-
569
- const proto = await protoFor(data)
570
- // TODO: store data automaticly or not
571
- return BufferToUint8Array(proto.decoded.data)
572
-
573
- // this.put(hash, proto.decoded.data)
574
- } else {
575
- this.dht.removeProvider(id, hash)
576
- }
577
- return
578
- }
579
-
580
- get message() {
581
- return {
582
- /**
583
- * Get content for given message hash
584
- *
585
- * @param {String} hash
586
- */
587
- get: async (hash) => {
588
- debug(`get message ${hash}`)
589
- const message = await messageStore.has(hash)
590
- if (message) return await messageStore.get(hash)
591
- return this.requestData(hash, 'message')
592
- },
593
- /**
594
- * put message content
595
- *
596
- * @param {String} hash
597
- * @param {Buffer} message
598
- */
599
- put: async (hash, message) => await messageStore.put(hash, message),
600
- /**
601
- * @param {String} hash
602
- * @return {Boolean}
603
- */
604
- has: async (hash) => await messageStore.has(hash)
605
- }
606
- }
607
-
608
- get data() {
609
- return {
610
- /**
611
- * Get content for given data hash
612
- *
613
- * @param {String} hash
614
- */
615
- get: async (hash) => {
616
- debug(`get data ${hash}`)
617
- const data = await dataStore.has(hash)
618
- if (data) return await dataStore.get(hash)
619
- return this.requestData(hash, 'data')
620
- },
621
- /**
622
- * put data content
623
- *
624
- * @param {String} hash
625
- * @param {Buffer} data
626
- */
627
- put: async (hash, data) => await dataStore.put(hash, data),
628
- /**
629
- * @param {String} hash
630
- * @return {Boolean}
631
- */
632
- has: async (hash) => await dataStore.has(hash)
633
- }
634
- }
635
-
636
- get folder() {
637
- return {
638
- /**
639
- * Get content for given data hash
640
- *
641
- * @param {String} hash
642
- */
643
- get: async (hash) => {
644
- debug(`get data ${hash}`)
645
- const data = await dataStore.has(hash)
646
- if (data) return await dataStore.get(hash)
647
- return this.requestData(hash, 'data')
648
- },
649
- /**
650
- * put data content
651
- *
652
- * @param {String} hash
653
- * @param {Buffer} data
654
- */
655
- put: async (hash, data) => await dataStore.put(hash, data),
656
- /**
657
- * @param {String} hash
658
- * @return {Boolean}
659
- */
660
- has: async (hash) => await dataStore.has(hash)
661
- }
662
- }
663
-
664
- async addFolder(files) {
665
- const links = []
666
- for (const file of files) {
667
- const fileNode = await new globalThis.peernet.protos['peernet-file'](file)
668
- const hash = await fileNode.hash
669
- await dataStore.put(hash, fileNode.encoded)
670
- links.push({ hash, path: file.path })
671
- }
672
- const node = await new globalThis.peernet.protos['peernet-file']({
673
- path: '/',
674
- links
675
- })
676
- const hash = await node.hash
677
- await dataStore.put(hash, node.encoded)
678
-
679
- return hash
680
- }
681
-
682
- async ls(hash, options) {
683
- let data
684
- const has = await dataStore.has(hash)
685
- data = has ? await dataStore.get(hash) : await this.requestData(hash, 'data')
686
-
687
- const node = await new peernet.protos['peernet-file'](data)
688
- await node.decode()
689
- console.log(data)
690
- const paths = []
691
- if (node.decoded?.links.length === 0) throw new Error(`${hash} is a file`)
692
- for (const { path, hash } of node.decoded.links) {
693
- paths.push({ path, hash })
694
- }
695
- if (options?.pin) await dataStore.put(hash, node.encoded)
696
- return paths
697
- }
698
-
699
- async cat(hash, options) {
700
- let data
701
- const has = await dataStore.has(hash)
702
- data = has ? await dataStore.get(hash) : await this.requestData(hash, 'data')
703
- const node = await new peernet.protos['peernet-file'](data)
704
-
705
- if (node.decoded?.links.length > 0) throw new Error(`${hash} is a directory`)
706
- if (options?.pin) await dataStore.put(hash, node.encoded)
707
- return node.decoded.content
708
- }
709
-
710
- /**
711
- * goes trough given stores and tries to find data for given hash
712
- * @param {Array} stores
713
- * @param {string} hash
714
- */
715
- async whichStore(stores, hash) {
716
- let store = stores.pop()
717
- const name = store
718
- store = globalThis[`${store}Store`]
719
- if (store) {
720
- const has = await store.has(hash)
721
- if (has) return store
722
- if (stores.length > 0) return this.whichStore(stores, hash)
723
- } else return
724
- }
725
-
726
- /**
727
- * Get content for given hash
728
- *
729
- * @param {String} hash - the hash of the wanted data
730
- * @param {String} store - storeName to access
731
- */
732
- async get(hash, store) {
733
- debug(`get ${hash}`)
734
- let data
735
- if (store) store = globalThis[`${store}Store`]
736
- if (!store) store = await this.whichStore([...this.stores], hash)
737
- if (store && (await store.has(hash))) data = await store.get(hash)
738
- if (data) return data
739
-
740
- return this.requestData(hash, store?.name || store)
741
- }
742
-
743
- /**
744
- * put content
745
- *
746
- * @param {String} hash
747
- * @param {Buffer} data
748
- * @param {String} storeName - storeName to access
749
- */
750
- async put(hash: string, data: Uint8Array, storeName: string | LeofcoinStorageClass = 'data') {
751
- const store: LeofcoinStorageClass = globalThis[`${storeName}Store`]
752
- return store.put(hash, data)
753
- }
754
-
755
- /**
756
- * @param {String} hash
757
- * @return {Boolean}
758
- */
759
- async has(hash) {
760
- const store = await this.whichStore([...this.stores], hash)
761
- if (store) {
762
- return store.private ? false : true
763
- }
764
- return false
765
- }
766
-
767
- /**
768
- *
769
- * @param {String} topic
770
- * @param {String|Object|Array|Boolean|Buffer} data
771
- */
772
- async publish(topic, data) {
773
- // globalSub.publish(topic, data)
774
- const id = Math.random().toString(36).slice(-12)
775
- data = await new globalThis.peernet.protos['peernet-ps']({ data, topic })
776
- for (const [peerId, peer] of Object.entries(this.connections)) {
777
- if (peerId !== this.id) {
778
- const node = await this.prepareMessage(data)
779
- this.sendMessage(peer, id, node.encoded)
780
- }
781
- // TODO: if peer subscribed
782
- }
783
- }
784
-
785
- // createHash(data, name) {
786
- // return new CodeHash(data, {name})
787
- // }
788
-
789
- /**
790
- *
791
- * @param {String} topic
792
- * @param {Method} cb
793
- */
794
- async subscribe(topic: string, callback: Function) {
795
- // TODO: if peer subscribed
796
- globalSub.subscribe(topic, callback)
797
- }
798
-
799
- async removePeer(peer) {
800
- console.log('removepeer', peer.id)
801
- const id = peer.id
802
- // await this.client.connections(peer)
803
- // if (this.client.peers[id]) {
804
- // for (const connection of Object.keys(this.client.peers[id])) {
805
- // // if (this.client.peers[id][connection].connected === false) delete this.client.peers[id][connection]
806
- // // @ts-ignore
807
- // if (this.client.peers[id][connection].connected) return this.client.emit('peerconnect', connection)
808
- // }
809
- // }
810
- }
811
-
812
- get Buffer() {
813
- return Buffer
814
- }
815
- }
816
-
817
- globalThis.Peernet = Peernet
1
+ import '@vandeurenglenn/debug'
2
+ import PubSub from '@vandeurenglenn/little-pubsub'
3
+ import PeerDiscovery from './discovery/peer-discovery.js'
4
+ import DHT, { DHTProvider, DHTProviderDistanceResult } from './dht/dht.js'
5
+ import { BufferToUint8Array, protoFor, target } from './utils/utils.js'
6
+ import MessageHandler from './handlers/message.js'
7
+ import dataHandler from './handlers/data.js'
8
+ import { dhtError, nothingFoundError } from './errors/errors.js'
9
+ import { Storage as LeofcoinStorageClass } from '@leofcoin/storage'
10
+ import { utils as codecUtils } from '@leofcoin/codecs'
11
+ import Identity from './identity.js'
12
+ import swarm from '@netpeer/swarm/client'
13
+
14
+ globalThis.LeofcoinStorage = LeofcoinStorageClass
15
+
16
+ globalThis.leofcoin = globalThis.leofcoin || {}
17
+ globalThis.pubsub = globalThis.pubsub || new PubSub()
18
+ globalThis.globalSub = globalThis.globalSub || new PubSub()
19
+
20
+ declare global {
21
+ var LeofcoinStorage: typeof LeofcoinStorageClass
22
+ var peernet: Peernet
23
+ var pubsub: PubSub
24
+ var globalSub: PubSub
25
+ var blockStore: LeofcoinStorageClass
26
+ var transactionStore: LeofcoinStorageClass
27
+ var messageStore: LeofcoinStorageClass
28
+ var dataStore: LeofcoinStorageClass
29
+ var walletStore: LeofcoinStorageClass
30
+ var chainStore: LeofcoinStorageClass
31
+ }
32
+
33
+ const debug = globalThis.createDebugger('peernet')
34
+ /**
35
+ * @access public
36
+ * @example
37
+ * const peernet = new Peernet();
38
+ */
39
+ export default class Peernet {
40
+ storePrefix: string
41
+ root: string
42
+ identity: Identity
43
+ stores: string[] = []
44
+ peerId: string
45
+ /**
46
+ * @type {Object}
47
+ * @property {Object} peer Instance of Peer
48
+ */
49
+ dht: DHT = new DHT()
50
+ /** @leofcoin/peernet-swarm/client */
51
+ client: swarm
52
+ network: string
53
+ stars: string[]
54
+ networkVersion: string
55
+ bw: {
56
+ up: number
57
+ down: number
58
+ }
59
+ hasDaemon: boolean = false
60
+ autoStart: boolean = true
61
+ #starting: boolean = false
62
+ #started: boolean = false
63
+ requestProtos = {}
64
+ _messageHandler: MessageHandler
65
+ _peerHandler: PeerDiscovery
66
+ protos: {}
67
+ version
68
+
69
+ /**
70
+ * @access public
71
+ * @param {Object} options
72
+ * @param {String} options.network - desired network
73
+ * @param {String} options.stars - star list for selected network (these should match, don't mix networks)
74
+ * @param {String} options.root - path to root directory
75
+ * @param {String} options.version - path to root directory
76
+ * @param {String} options.storePrefix - prefix for datatores (lfc)
77
+ *
78
+ * @return {Promise} instance of Peernet
79
+ *
80
+ * @example
81
+ * const peernet = new Peernet({network: 'leofcoin', root: '.leofcoin'});
82
+ */
83
+ constructor(options, password) {
84
+ /**
85
+ * @property {String} network - current network
86
+ */
87
+ this.network = options.network || 'leofcoin'
88
+ this.autoStart = options.autoStart === undefined ? true : options.autoStart
89
+ this.stars = options.stars
90
+ this.version = options.version
91
+ const parts = this.network.split(':')
92
+ this.networkVersion = options.networkVersion || parts.length > 1 ? parts[1] : 'mainnet'
93
+
94
+ if (!options.storePrefix) options.storePrefix = 'lfc'
95
+ if (!options.port) options.port = 2000
96
+ if (!options.root) {
97
+ parts[1] ? (options.root = `.${parts[0]}/${parts[1]}`) : (options.root = `.${this.network}`)
98
+ }
99
+
100
+ globalThis.peernet = this
101
+ this.bw = {
102
+ up: 0,
103
+ down: 0
104
+ }
105
+ // @ts-ignore
106
+ return this._init(options, password)
107
+ }
108
+
109
+ get id() {
110
+ return this.identity.id
111
+ }
112
+
113
+ get selectedAccount(): string {
114
+ return this.identity.selectedAccount
115
+ }
116
+
117
+ get accounts(): Promise<[[name: string, externalAddress: string, internalAddress: string]]> {
118
+ return this.identity.accounts
119
+ }
120
+
121
+ get defaultStores() {
122
+ return ['account', 'wallet', 'block', 'transaction', 'chain', 'data', 'message']
123
+ }
124
+
125
+ selectAccount(account: string) {
126
+ return this.identity.selectAccount(account)
127
+ }
128
+
129
+ addProto(name, proto) {
130
+ if (!globalThis.peernet.protos[name]) globalThis.peernet.protos[name] = proto
131
+ }
132
+
133
+ addCodec(codec) {
134
+ return codecUtils.addCodec(codec)
135
+ }
136
+
137
+ async addStore(name, prefix, root, isPrivate = true) {
138
+ if (name === 'block' || name === 'transaction' || name === 'chain' || name === 'data' || name === 'message')
139
+ isPrivate = false
140
+
141
+ let Storage
142
+
143
+ this.hasDaemon ? (Storage = LeofcoinStorageClient) : (Storage = LeofcoinStorage)
144
+
145
+ if (!globalThis[`${name}Store`]) {
146
+ globalThis[`${name}Store`] = new Storage(name, root)
147
+ await globalThis[`${name}Store`].init()
148
+ }
149
+
150
+ globalThis[`${name}Store`].private = isPrivate
151
+ if (!isPrivate) this.stores.push(name)
152
+ }
153
+
154
+ /**
155
+ * @see MessageHandler
156
+ */
157
+ prepareMessage(data) {
158
+ return this._messageHandler.prepareMessage(data)
159
+ }
160
+
161
+ /**
162
+ * @access public
163
+ *
164
+ * @return {Array} peerId
165
+ */
166
+ get peers() {
167
+ return Object.entries(this.client?.connections || {})
168
+ }
169
+
170
+ get connections() {
171
+ return this.client?.connections || {}
172
+ }
173
+
174
+ /**
175
+ * @return {String} id - peerId
176
+ */
177
+ getConnection(id) {
178
+ return this.connections[id]
179
+ }
180
+
181
+ /**
182
+ * @private
183
+ *
184
+ * @param {Object} options
185
+ * @param {String} options.root - path to root directory
186
+ *
187
+ * @return {Promise} instance of Peernet
188
+ */
189
+ async _init(options: { storePrefix?: string; root?: string }, password: string): Promise<Peernet> {
190
+ this.storePrefix = options.storePrefix
191
+ this.root = options.root
192
+
193
+ const {
194
+ RequestMessage,
195
+ ResponseMessage,
196
+ PeerMessage,
197
+ PeerMessageResponse,
198
+ PeernetMessage,
199
+ DHTMessage,
200
+ DHTMessageResponse,
201
+ DataMessage,
202
+ DataMessageResponse,
203
+ PsMessage,
204
+ ChatMessage,
205
+ PeernetFile
206
+ // FolderMessageResponse
207
+ } = await import(/* webpackChunkName: "messages" */ './messages.js')
208
+
209
+ /**
210
+ * proto Object containing protos
211
+ * @type {Object}
212
+ * @property {PeernetMessage} protos[peernet-message] messageNode
213
+ * @property {DHTMessage} protos[peernet-dht] messageNode
214
+ * @property {DHTMessageResponse} protos[peernet-dht-response] messageNode
215
+ * @property {DataMessage} protos[peernet-data] messageNode
216
+ * @property {DataMessageResponse} protos[peernet-data-response] messageNode
217
+ */
218
+
219
+ globalThis.peernet.protos = {
220
+ 'peernet-request': RequestMessage,
221
+ 'peernet-response': ResponseMessage,
222
+ 'peernet-peer': PeerMessage,
223
+ 'peernet-peer-response': PeerMessageResponse,
224
+ 'peernet-message': PeernetMessage,
225
+ 'peernet-dht': DHTMessage,
226
+ 'peernet-dht-response': DHTMessageResponse,
227
+ 'peernet-data': DataMessage,
228
+ 'peernet-data-response': DataMessageResponse,
229
+ 'peernet-ps': PsMessage,
230
+ 'chat-message': ChatMessage,
231
+ 'peernet-file': PeernetFile
232
+ }
233
+
234
+ this._messageHandler = new MessageHandler(this.network)
235
+
236
+ const { daemon, environment } = await target()
237
+ this.hasDaemon = daemon
238
+
239
+ for (const store of this.defaultStores) {
240
+ await this.addStore(store, options.storePrefix, options.root)
241
+ }
242
+
243
+ this.identity = new Identity(this.network)
244
+ await this.identity.load(password)
245
+
246
+ this._peerHandler = new PeerDiscovery(this.id)
247
+ this.peerId = this.id
248
+
249
+ // this.addRequestHandler('handshake', () => {
250
+ // return new peernet.protos['peernet-response']({
251
+ // response: { peerId: this.id }
252
+ // })
253
+ // })
254
+
255
+ // pubsub.subscribe('peer:discovered', async (peer) => {
256
+ // // console.log(peer);
257
+
258
+ // if (this.requestProtos['version'] && !peer.version) {
259
+ // let data = await new globalThis.peernet.protos['peernet-request']({
260
+ // request: 'version'
261
+ // })
262
+ // let node = await globalThis.peernet.prepareMessage(data)
263
+ // let response = await peer.request(node.encoded)
264
+ // response = await new globalThis.peernet.protos['peernet-response'](new Uint8Array(Object.values(response)))
265
+ // peer.version = response.decoded.response.version
266
+ // }
267
+
268
+ // if (!peer.peerId) {
269
+ // let data = await new globalThis.peernet.protos['peernet-request']({
270
+ // request: 'handshake'
271
+ // })
272
+ // let node = await globalThis.peernet.prepareMessage(data)
273
+ // let response = await peer.request(node.encoded)
274
+
275
+ // response = await new globalThis.peernet.protos['peernet-response'](new Uint8Array(Object.values(response)))
276
+ // // todo: response.decoded should be the response and not response.peerId
277
+ // // todo: ignore above and remove discover completly
278
+ // response.decoded.response.peerId
279
+ // }
280
+
281
+ // this.connections[peer.peerId] = peer
282
+ // pubsub.publish('peer:connected', peer)
283
+ // todo: cleanup discovered
284
+ // })
285
+
286
+ // pubsub.subscribe('peer:left', this.#peerLeft.bind(this))
287
+
288
+ /**
289
+ * converts data -> message -> proto
290
+ * @see DataHandler
291
+ */
292
+ pubsub.subscribe('peer:data', dataHandler)
293
+
294
+ // // todo: remove below, already handles in the swarm
295
+ // if (globalThis.navigator) {
296
+ // globalThis.addEventListener('beforeunload', async () => this.client.close())
297
+ // } else {
298
+ // process.on('SIGTERM', async () => {
299
+ // process.stdin.resume()
300
+ // try {
301
+ // await this.client.close()
302
+ // } catch (error) {
303
+ // // @ts-ignore
304
+ // await this.client.close()
305
+ // }
306
+ // process.exit()
307
+ // })
308
+ // }
309
+ if (this.autoStart) await this.start()
310
+ return this
311
+ }
312
+
313
+ async start() {
314
+ if (this.#starting || this.#started) return
315
+
316
+ this.#starting = true
317
+ const importee = await import('@netpeer/swarm/client')
318
+ /**
319
+ * @access public
320
+ * @type {PeernetClient}
321
+ */
322
+ console.log(this.stars)
323
+
324
+ this.client = new importee.default({
325
+ peerId: this.id,
326
+ networkVersion: this.networkVersion,
327
+ version: this.version,
328
+ stars: this.stars
329
+ })
330
+ this.#started = true
331
+ this.#starting = false
332
+ }
333
+
334
+ // todo: remove, handled in swarm now
335
+ // #peerLeft(peer: SwarmPeer) {
336
+ // for (const [id, _peer] of Object.entries(this.connections)) {
337
+ // if (_peer.id === peer.id && this.connections[id] && !this.connections[id].connected) {
338
+ // delete this.connections[id]
339
+ // this.removePeer(_peer)
340
+ // }
341
+ // }
342
+ // }
343
+
344
+ addRequestHandler(name, method) {
345
+ this.requestProtos[name] = method
346
+ }
347
+
348
+ async sendMessage(peer, id, data) {
349
+ if (peer.connected) {
350
+ await peer.send(data, id)
351
+ this.bw.up += data.length
352
+ return id
353
+ } else {
354
+ return new Promise((resolve, reject) => {
355
+ const onError = (error) => {
356
+ this.removePeer(peer)
357
+ reject(error)
358
+ }
359
+ peer.once('error', onError)
360
+ peer.once('connect', async () => {
361
+ await peer.send(data, id)
362
+ this.bw.up += data.length
363
+ peer.removeListener('error', onError)
364
+ resolve(id)
365
+ })
366
+ })
367
+ }
368
+ }
369
+
370
+ async handleDHT(peer, id, proto) {
371
+ let { hash, store } = proto.decoded
372
+ let has
373
+
374
+ if (store) {
375
+ store = globalThis[`${store}Store`]
376
+ has = store.private ? false : await store.has(hash)
377
+ } else {
378
+ has = await this.has(hash)
379
+ }
380
+
381
+ const data = await new globalThis.peernet.protos['peernet-dht-response']({
382
+ hash,
383
+ has
384
+ })
385
+ const node = await this.prepareMessage(data)
386
+
387
+ this.sendMessage(peer, id, node.encoded)
388
+ }
389
+
390
+ async handleData(peer, id, proto) {
391
+ let { hash, store } = proto.decoded
392
+ let data
393
+ try {
394
+ store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash))
395
+
396
+ if (store && !store.private) {
397
+ data = await store.get(hash)
398
+
399
+ if (data) {
400
+ data = await new globalThis.peernet.protos['peernet-data-response']({
401
+ hash,
402
+ data
403
+ })
404
+
405
+ const node = await this.prepareMessage(data)
406
+ this.sendMessage(peer, id, node.encoded)
407
+ }
408
+ } else {
409
+ // ban (trying to access private st)
410
+ }
411
+ } catch (error) {
412
+ return this.requestData(hash, store)
413
+ }
414
+ }
415
+
416
+ async handleRequest(peer, id, proto) {
417
+ const method = this.requestProtos[proto.decoded.request]
418
+ if (method) {
419
+ const data = await method(proto.decoded.requested)
420
+ const node = await this.prepareMessage(data)
421
+ this.sendMessage(peer, id, node.encoded)
422
+ }
423
+ }
424
+
425
+ /**
426
+ * @private
427
+ *
428
+ * @param {Buffer} message - peernet message
429
+ * @param {PeernetPeer} peer - peernet peer
430
+ */
431
+ async _protoHandler(message, peer, from) {
432
+ const { id, proto } = message
433
+
434
+ this.bw.down += proto.encoded.length
435
+ switch (proto.name) {
436
+ case 'peernet-dht': {
437
+ this.handleDHT(peer, id, proto)
438
+ break
439
+ }
440
+ case 'peernet-data': {
441
+ this.handleData(peer, id, proto)
442
+ break
443
+ }
444
+ case 'peernet-request': {
445
+ this.handleRequest(peer, id, proto)
446
+ break
447
+ }
448
+
449
+ case 'peernet-ps': {
450
+ globalSub.publish(new TextDecoder().decode(proto.decoded.topic), proto.decoded.data)
451
+ }
452
+ }
453
+ }
454
+
455
+ /**
456
+ * performs a walk and resolves first encounter
457
+ *
458
+ * @param {String} hash
459
+ */
460
+ async walk(hash) {
461
+ if (!hash) throw new Error('hash expected, received undefined')
462
+ const data = await new globalThis.peernet.protos['peernet-dht']({ hash })
463
+ const walk = async (peer, peerId) => {
464
+ const node = await this.prepareMessage(data)
465
+ let result = await peer.request(node.encoded)
466
+ result = new Uint8Array(Object.values(result))
467
+ const proto = await protoFor(result)
468
+ if (proto.name !== 'peernet-dht-response') throw dhtError(proto.name)
469
+
470
+ const peerInfo = {
471
+ ...peer.connectionStats,
472
+ id: peerId
473
+ }
474
+
475
+ if (proto.decoded.has) this.dht.addProvider(peerInfo, proto.decoded.hash)
476
+ }
477
+ let walks = []
478
+ for (const [peerId, peer] of Object.entries(this.connections)) {
479
+ if (peerId !== this.id) {
480
+ walks.push(walk(peer, peerId))
481
+ }
482
+ }
483
+ return Promise.all(walks)
484
+ }
485
+
486
+ /**
487
+ * Override DHT behavior, try's finding the content three times
488
+ *
489
+ * @param {String} hash
490
+ */
491
+ async providersFor(hash: string, store?: undefined) {
492
+ let providers = this.dht.providersFor(hash)
493
+ // walk the network to find a provider
494
+ let tries = 0
495
+ while ((!providers && tries < 3) || (providers && Object.keys(providers).length === 0 && tries < 3)) {
496
+ tries += 1
497
+ await this.walk(hash)
498
+ providers = this.dht.providersFor(hash)
499
+ }
500
+ // undefined if no providers given
501
+ return providers
502
+ }
503
+
504
+ get block() {
505
+ return {
506
+ get: async (hash: string) => {
507
+ const data = await blockStore.has(hash)
508
+ if (data) return blockStore.get(hash)
509
+ return this.requestData(hash, 'block')
510
+ },
511
+ put: async (hash: string, data: Uint8Array) => {
512
+ if (await blockStore.has(hash)) return
513
+ return await blockStore.put(hash, data)
514
+ },
515
+ has: async (hash: string) => await blockStore.has(hash)
516
+ }
517
+ }
518
+
519
+ get transaction() {
520
+ return {
521
+ get: async (hash: string) => {
522
+ const data = await transactionStore.has(hash)
523
+ if (data) return await transactionStore.get(hash)
524
+ return this.requestData(hash, 'transaction')
525
+ },
526
+ put: async (hash: string, data: Uint8Array) => {
527
+ if (await transactionStore.has(hash)) return
528
+ return await transactionStore.put(hash, data)
529
+ },
530
+ has: async (hash: string) => await transactionStore.has(hash)
531
+ }
532
+ }
533
+
534
+ async requestData(hash, store) {
535
+ const providers = await this.providersFor(hash)
536
+ if (!providers || (providers && Object.keys(providers).length === 0)) throw nothingFoundError(hash)
537
+ debug(`found ${Object.keys(providers).length} provider(s) for ${hash}`)
538
+ // get closest peer on earth
539
+ const closestPeer: DHTProvider = Object.values(providers)[0]
540
+
541
+ // get peer instance by id
542
+ if (!closestPeer || !closestPeer.id) return this.requestData(hash, store?.name || store)
543
+
544
+ const id = closestPeer.id
545
+ const peer = this.connections[id]
546
+
547
+ if (peer?.connected) {
548
+ let data = await new globalThis.peernet.protos['peernet-data']({
549
+ hash,
550
+ store: store?.name || store
551
+ })
552
+
553
+ const node = await this.prepareMessage(data)
554
+
555
+ if (peer) data = await peer.request(node.encoded)
556
+ else {
557
+ // fallback and try every provider found
558
+ const promises = []
559
+ const providers = await this.providersFor(hash, store)
560
+ for (const provider of Object.values(providers)) {
561
+ const peer = this.connections[provider.id]
562
+
563
+ if (peer) promises.push(peer.request(node.encoded))
564
+ }
565
+ data = await Promise.race(promises)
566
+ }
567
+ if (data) data = new Uint8Array(Object.values(data))
568
+
569
+ const proto = await protoFor(data)
570
+ // TODO: store data automaticly or not
571
+ return BufferToUint8Array(proto.decoded.data)
572
+
573
+ // this.put(hash, proto.decoded.data)
574
+ } else {
575
+ this.dht.removeProvider(id, hash)
576
+ }
577
+ return
578
+ }
579
+
580
+ get message() {
581
+ return {
582
+ /**
583
+ * Get content for given message hash
584
+ *
585
+ * @param {String} hash
586
+ */
587
+ get: async (hash) => {
588
+ debug(`get message ${hash}`)
589
+ const message = await messageStore.has(hash)
590
+ if (message) return await messageStore.get(hash)
591
+ return this.requestData(hash, 'message')
592
+ },
593
+ /**
594
+ * put message content
595
+ *
596
+ * @param {String} hash
597
+ * @param {Buffer} message
598
+ */
599
+ put: async (hash, message) => await messageStore.put(hash, message),
600
+ /**
601
+ * @param {String} hash
602
+ * @return {Boolean}
603
+ */
604
+ has: async (hash) => await messageStore.has(hash)
605
+ }
606
+ }
607
+
608
+ get data() {
609
+ return {
610
+ /**
611
+ * Get content for given data hash
612
+ *
613
+ * @param {String} hash
614
+ */
615
+ get: async (hash) => {
616
+ debug(`get data ${hash}`)
617
+ const data = await dataStore.has(hash)
618
+ if (data) return await dataStore.get(hash)
619
+ return this.requestData(hash, 'data')
620
+ },
621
+ /**
622
+ * put data content
623
+ *
624
+ * @param {String} hash
625
+ * @param {Buffer} data
626
+ */
627
+ put: async (hash, data) => await dataStore.put(hash, data),
628
+ /**
629
+ * @param {String} hash
630
+ * @return {Boolean}
631
+ */
632
+ has: async (hash) => await dataStore.has(hash)
633
+ }
634
+ }
635
+
636
+ get folder() {
637
+ return {
638
+ /**
639
+ * Get content for given data hash
640
+ *
641
+ * @param {String} hash
642
+ */
643
+ get: async (hash) => {
644
+ debug(`get data ${hash}`)
645
+ const data = await dataStore.has(hash)
646
+ if (data) return await dataStore.get(hash)
647
+ return this.requestData(hash, 'data')
648
+ },
649
+ /**
650
+ * put data content
651
+ *
652
+ * @param {String} hash
653
+ * @param {Buffer} data
654
+ */
655
+ put: async (hash, data) => await dataStore.put(hash, data),
656
+ /**
657
+ * @param {String} hash
658
+ * @return {Boolean}
659
+ */
660
+ has: async (hash) => await dataStore.has(hash)
661
+ }
662
+ }
663
+
664
+ async addFolder(files) {
665
+ const links = []
666
+ for (const file of files) {
667
+ const fileNode = await new globalThis.peernet.protos['peernet-file'](file)
668
+ const hash = await fileNode.hash
669
+ await dataStore.put(hash, fileNode.encoded)
670
+ links.push({ hash, path: file.path })
671
+ }
672
+ const node = await new globalThis.peernet.protos['peernet-file']({
673
+ path: '/',
674
+ links
675
+ })
676
+ const hash = await node.hash
677
+ await dataStore.put(hash, node.encoded)
678
+
679
+ return hash
680
+ }
681
+
682
+ async ls(hash, options) {
683
+ let data
684
+ const has = await dataStore.has(hash)
685
+ data = has ? await dataStore.get(hash) : await this.requestData(hash, 'data')
686
+
687
+ const node = await new peernet.protos['peernet-file'](data)
688
+ await node.decode()
689
+ console.log(data)
690
+ const paths = []
691
+ if (node.decoded?.links.length === 0) throw new Error(`${hash} is a file`)
692
+ for (const { path, hash } of node.decoded.links) {
693
+ paths.push({ path, hash })
694
+ }
695
+ if (options?.pin) await dataStore.put(hash, node.encoded)
696
+ return paths
697
+ }
698
+
699
+ async cat(hash, options) {
700
+ let data
701
+ const has = await dataStore.has(hash)
702
+ data = has ? await dataStore.get(hash) : await this.requestData(hash, 'data')
703
+ const node = await new peernet.protos['peernet-file'](data)
704
+
705
+ if (node.decoded?.links.length > 0) throw new Error(`${hash} is a directory`)
706
+ if (options?.pin) await dataStore.put(hash, node.encoded)
707
+ return node.decoded.content
708
+ }
709
+
710
+ /**
711
+ * goes trough given stores and tries to find data for given hash
712
+ * @param {Array} stores
713
+ * @param {string} hash
714
+ */
715
+ async whichStore(stores, hash) {
716
+ let store = stores.pop()
717
+ const name = store
718
+ store = globalThis[`${store}Store`]
719
+ if (store) {
720
+ const has = await store.has(hash)
721
+ if (has) return store
722
+ if (stores.length > 0) return this.whichStore(stores, hash)
723
+ } else return
724
+ }
725
+
726
+ /**
727
+ * Get content for given hash
728
+ *
729
+ * @param {String} hash - the hash of the wanted data
730
+ * @param {String} store - storeName to access
731
+ */
732
+ async get(hash, store) {
733
+ debug(`get ${hash}`)
734
+ let data
735
+ if (store) store = globalThis[`${store}Store`]
736
+ if (!store) store = await this.whichStore([...this.stores], hash)
737
+ if (store && (await store.has(hash))) data = await store.get(hash)
738
+ if (data) return data
739
+
740
+ return this.requestData(hash, store?.name || store)
741
+ }
742
+
743
+ /**
744
+ * put content
745
+ *
746
+ * @param {String} hash
747
+ * @param {Buffer} data
748
+ * @param {String} storeName - storeName to access
749
+ */
750
+ async put(hash: string, data: Uint8Array, storeName: string | LeofcoinStorageClass = 'data') {
751
+ const store: LeofcoinStorageClass = globalThis[`${storeName}Store`]
752
+ return store.put(hash, data)
753
+ }
754
+
755
+ /**
756
+ * @param {String} hash
757
+ * @return {Boolean}
758
+ */
759
+ async has(hash) {
760
+ const store = await this.whichStore([...this.stores], hash)
761
+ if (store) {
762
+ return store.private ? false : true
763
+ }
764
+ return false
765
+ }
766
+
767
+ /**
768
+ *
769
+ * @param {String} topic
770
+ * @param {String|Object|Array|Boolean|Buffer} data
771
+ */
772
+ async publish(topic, data) {
773
+ // globalSub.publish(topic, data)
774
+ const id = Math.random().toString(36).slice(-12)
775
+ data = await new globalThis.peernet.protos['peernet-ps']({ data, topic })
776
+ for (const [peerId, peer] of Object.entries(this.connections)) {
777
+ if (peerId !== this.id) {
778
+ const node = await this.prepareMessage(data)
779
+ this.sendMessage(peer, id, node.encoded)
780
+ }
781
+ // TODO: if peer subscribed
782
+ }
783
+ }
784
+
785
+ // createHash(data, name) {
786
+ // return new CodeHash(data, {name})
787
+ // }
788
+
789
+ /**
790
+ *
791
+ * @param {String} topic
792
+ * @param {Method} cb
793
+ */
794
+ async subscribe(topic: string, callback: Function) {
795
+ // TODO: if peer subscribed
796
+ globalSub.subscribe(topic, callback)
797
+ }
798
+
799
+ async removePeer(peer) {
800
+ console.log('removepeer', peer.id)
801
+ const id = peer.id
802
+ // await this.client.connections(peer)
803
+ // if (this.client.peers[id]) {
804
+ // for (const connection of Object.keys(this.client.peers[id])) {
805
+ // // if (this.client.peers[id][connection].connected === false) delete this.client.peers[id][connection]
806
+ // // @ts-ignore
807
+ // if (this.client.peers[id][connection].connected) return this.client.emit('peerconnect', connection)
808
+ // }
809
+ // }
810
+ }
811
+
812
+ get Buffer() {
813
+ return Buffer
814
+ }
815
+ }
816
+
817
+ globalThis.Peernet = Peernet