@leofcoin/peernet 1.1.56 → 1.1.58

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