@leofcoin/peernet 1.1.57 → 1.1.59

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