@leofcoin/peernet 1.0.2 → 1.0.3

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