@leofcoin/peernet 2.0.0 → 3.0.0

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 (121) hide show
  1. package/docs/automatic-transport.md +37 -0
  2. package/exports/browser/{client-CAC7RVMf.js → client-C_pBoi1g.js} +252 -76
  3. package/exports/browser/{messages-Dw86z0zq.js → messages-BGHaihSi.js} +1 -1
  4. package/exports/browser/{peernet-B2jRhqGI.js → peernet-Db43ag2D.js} +2 -2
  5. package/exports/browser/peernet.js +1 -1
  6. package/exports/store.js +0 -3
  7. package/package.json +8 -4
  8. package/.esdoc.json +0 -10
  9. package/.gitattributes +0 -2
  10. package/.github/workflows/release.yml +0 -34
  11. package/.prettierrc +0 -7
  12. package/.travis.yml +0 -27
  13. package/BREAKING_CHANGES.md +0 -35
  14. package/deploy.js +0 -8
  15. package/exports/browser/browser-D-r0O9Qn.js +0 -53
  16. package/exports/browser/browser-DvU1xNFS.js +0 -147
  17. package/exports/browser/browser-_hiyXwPp.js +0 -147
  18. package/exports/browser/client-86Tnax66.js +0 -2224
  19. package/exports/browser/client-BFxJts6j.js +0 -2224
  20. package/exports/browser/client-BVhUamQG.js +0 -2200
  21. package/exports/browser/client-BzvWzzLv.js +0 -2224
  22. package/exports/browser/client-C9GBz2Tf.js +0 -2224
  23. package/exports/browser/client-CQzuu6At.js +0 -2224
  24. package/exports/browser/client-CVkFejdx.js +0 -2224
  25. package/exports/browser/client-Cj_UUO8W.js +0 -2224
  26. package/exports/browser/client-CrGaeBjj.js +0 -2224
  27. package/exports/browser/client-DDC8C510.js +0 -2224
  28. package/exports/browser/client-ERocgKET.js +0 -2224
  29. package/exports/browser/client-a8rws3cu.js +0 -2224
  30. package/exports/browser/identity-CYVPDmkf.js +0 -8402
  31. package/exports/browser/identity-ChyyZSve.js +0 -8397
  32. package/exports/browser/index-BD0Anx7_.js +0 -37
  33. package/exports/browser/index-Bxr5Iztg.js +0 -37
  34. package/exports/browser/messages-BFCmg6Vb.js +0 -212
  35. package/exports/browser/messages-BHIC2mQu.js +0 -212
  36. package/exports/browser/messages-CimzJ4wr.js +0 -210
  37. package/exports/browser/messages-Cjoa_LGO.js +0 -210
  38. package/exports/browser/messages-D7JxkkDW.js +0 -212
  39. package/exports/browser/messages-DH0tgNM9.js +0 -207
  40. package/exports/browser/messages-DVHcNvs_.js +0 -212
  41. package/exports/browser/messages-DqL_g2qw.js +0 -210
  42. package/exports/browser/messages-UKnuelZ7.js +0 -207
  43. package/exports/browser/messages-XgZZUMma.js +0 -212
  44. package/exports/browser/messages-kfFyctPL.js +0 -210
  45. package/exports/browser/messages-psROnfRi.js +0 -212
  46. package/exports/browser/peernet-B9VL4Xwe.js +0 -9522
  47. package/exports/browser/peernet-C-HNdrcd.js +0 -9528
  48. package/exports/browser/peernet-C4rOvyyU.js +0 -9661
  49. package/exports/browser/peernet-Ca1iM2tN.js +0 -9336
  50. package/exports/browser/peernet-CbI0-jFn.js +0 -9651
  51. package/exports/browser/peernet-CtHScDyh.js +0 -9524
  52. package/exports/browser/peernet-DfuxUK3u.js +0 -9525
  53. package/exports/browser/peernet-DiEcNDLD.js +0 -9655
  54. package/exports/browser/peernet-DtuRXOzP.js +0 -9653
  55. package/exports/browser/peernet-r_9IyzTe.js +0 -9336
  56. package/exports/browser/peernet-rcD5XL1y.js +0 -9590
  57. package/exports/browser/peernet-rw8HJGOz.js +0 -9597
  58. package/exports/messages-CHKVVTVE.js +0 -204
  59. package/exports/messages-DaUtFfBh.js +0 -207
  60. package/index.html +0 -19
  61. package/rollup.config.js +0 -143
  62. package/src/dht/dht.ts +0 -142
  63. package/src/discovery/peer-discovery.js +0 -75
  64. package/src/errors/errors.js +0 -12
  65. package/src/file-transfer.ts +0 -194
  66. package/src/handlers/data.js +0 -15
  67. package/src/handlers/message.js +0 -34
  68. package/src/identity.ts +0 -172
  69. package/src/messages/chat.ts +0 -13
  70. package/src/messages/data-response.ts +0 -13
  71. package/src/messages/data.ts +0 -17
  72. package/src/messages/dht-response.ts +0 -13
  73. package/src/messages/dht.ts +0 -21
  74. package/src/messages/file-link.ts +0 -17
  75. package/src/messages/file.ts +0 -17
  76. package/src/messages/peer-response.ts +0 -13
  77. package/src/messages/peer.ts +0 -13
  78. package/src/messages/peernet.ts +0 -13
  79. package/src/messages/ps.ts +0 -13
  80. package/src/messages/request.ts +0 -13
  81. package/src/messages/response.ts +0 -13
  82. package/src/messages.js +0 -12
  83. package/src/peer-info.js +0 -9
  84. package/src/peernet.ts +0 -1113
  85. package/src/prompts/password/browser.js +0 -1
  86. package/src/prompts/password/node.js +0 -6
  87. package/src/prompts/password.js +0 -1
  88. package/src/proto/chat-message.proto.js +0 -6
  89. package/src/proto/data-response.proto.js +0 -4
  90. package/src/proto/data.proto.js +0 -4
  91. package/src/proto/dht-response.proto.js +0 -4
  92. package/src/proto/dht.proto.js +0 -4
  93. package/src/proto/file-link.proto.js +0 -5
  94. package/src/proto/file.proto.js +0 -10
  95. package/src/proto/peer-response.proto.js +0 -3
  96. package/src/proto/peer.proto.js +0 -3
  97. package/src/proto/peernet.proto.js +0 -7
  98. package/src/proto/ps.proto.js +0 -4
  99. package/src/proto/request.proto.js +0 -4
  100. package/src/proto/response.proto.js +0 -3
  101. package/src/types.ts +0 -25
  102. package/src/utils/utils.js +0 -77
  103. package/test/all.js +0 -5
  104. package/test/client.js +0 -15
  105. package/test/codec.js +0 -56
  106. package/test/file-transfer.test.js +0 -81
  107. package/test/fresh-empty-store.js +0 -15
  108. package/test/hash.js +0 -14
  109. package/test/index.js +0 -6
  110. package/test/lastBlock.js +0 -7
  111. package/test/messages.js +0 -26
  112. package/test/peer-data-exchange.test.js +0 -52
  113. package/test/peer-file-exchange.test.js +0 -78
  114. package/test/peernet.js +0 -17
  115. package/test/peernet.test.js +0 -477
  116. package/test-output.log +0 -84
  117. package/test.js +0 -63
  118. package/test2.js +0 -13
  119. package/test3.js +0 -16
  120. package/test4.js +0 -7
  121. package/tsconfig.json +0 -13
package/src/peernet.ts DELETED
@@ -1,1113 +0,0 @@
1
- import { createDebugger } from '@vandeurenglenn/debug'
2
- import PubSub, { Handler } from '@vandeurenglenn/little-pubsub'
3
- import PeerDiscovery from './discovery/peer-discovery.js'
4
- import DHT, { DHTProvider, DHTProviderDistanceResult, getAddress } from './dht/dht.js'
5
- import { BufferToUint8Array, protoFor, target } from './utils/utils.js'
6
- import MessageHandler from './handlers/message.js'
7
- import dataHandler from './handlers/data.js'
8
- import { dhtError, nothingFoundError } from './errors/errors.js'
9
- import { Storage as LeofcoinStorageClass } from '@leofcoin/storage'
10
- import { utils as codecUtils } from '@leofcoin/codecs'
11
- import Identity from './identity.js'
12
- import swarm from '@netpeer/swarm/client'
13
- import FileTransfer from './file-transfer.js'
14
-
15
- const DEFAULT_CHUNK_SIZE = 256 * 1024
16
- const DEFAULT_BLOCK_CHUNK_SIZE = 256 * 1024
17
- const DEFAULT_BLOCK_CHUNK_THRESHOLD = 1024 * 1024
18
- const DEFAULT_TRANSFER_CONCURRENCY = 4
19
-
20
- const adaptiveChunkSize = (size: number, minimum = DEFAULT_CHUNK_SIZE) => {
21
- if (size >= 256 * 1024 * 1024) return Math.max(minimum, 4 * 1024 * 1024)
22
- if (size >= 32 * 1024 * 1024) return Math.max(minimum, 1024 * 1024)
23
- return minimum
24
- }
25
-
26
- globalThis.LeofcoinStorage = LeofcoinStorageClass
27
-
28
- globalThis.leofcoin = globalThis.leofcoin || {}
29
- globalThis.pubsub = globalThis.pubsub || new PubSub()
30
- globalThis.globalSub = globalThis.globalSub || new PubSub()
31
-
32
- declare global {
33
- var LeofcoinStorage: typeof LeofcoinStorageClass
34
- var peernet: Peernet
35
- var pubsub: PubSub
36
- var globalSub: PubSub
37
- var blockStore: LeofcoinStorageClass
38
- var transactionStore: LeofcoinStorageClass
39
- var messageStore: LeofcoinStorageClass
40
- var dataStore: LeofcoinStorageClass
41
- var walletStore: LeofcoinStorageClass
42
- var chainStore: LeofcoinStorageClass
43
- var shareStore: LeofcoinStorageClass
44
- }
45
-
46
- const debug = createDebugger('peernet')
47
- /**
48
- * @access public
49
- * @example
50
- * const peernet = new Peernet();
51
- */
52
- export default class Peernet {
53
- storePrefix: string
54
- root: string
55
- identity: Identity
56
- stores: string[] = []
57
- peerId: string
58
- /**
59
- * @type {Object}
60
- * @property {Object} peer Instance of Peer
61
- */
62
- dht: DHT = new DHT()
63
- /** @leofcoin/peernet-swarm/client */
64
- client: swarm
65
- network: string
66
- stars: string[]
67
- transport
68
- networkVersion: string
69
- bw: {
70
- up: number
71
- down: number
72
- }
73
- hasDaemon: boolean = false
74
- autoStart: boolean = true
75
- #starting: boolean = false
76
- #started: boolean = false
77
- requestProtos = {}
78
- _messageHandler: MessageHandler
79
- _peerHandler: PeerDiscovery
80
- protos: {}
81
- version
82
- blockChunkSize: number
83
- blockChunkThreshold: number
84
- transferConcurrency: number
85
-
86
- #peerAttempts: { [key: string]: number } = {}
87
- private _inMemoryBroadcasts: any
88
- /**
89
- * @access public
90
- * @param {Object} options
91
- * @param {String} options.network - desired network
92
- * @param {String} options.stars - star list for selected network (these should match, don't mix networks)
93
- * @param {String} options.root - path to root directory
94
- * @param {String} options.version - path to root directory
95
- * @param {String} options.storePrefix - prefix for datatores (lfc)
96
- * @param {Object} options.transport - transport selection and fallback options passed to @netpeer/swarm
97
- *
98
- * @return {Promise} instance of Peernet
99
- *
100
- * @example
101
- * const peernet = new Peernet({network: 'leofcoin', root: '.leofcoin'});
102
- */
103
- constructor(options, password) {
104
- /**
105
- * @property {String} network - current network
106
- */
107
- this.network = options.network || 'leofcoin'
108
- this.autoStart = options.autoStart === undefined ? true : options.autoStart
109
- this.stars = options.stars
110
- this.transport = options.transport
111
- this.version = options.version
112
- this.blockChunkSize = options.blockChunkSize ?? DEFAULT_BLOCK_CHUNK_SIZE
113
- this.blockChunkThreshold = options.blockChunkThreshold ?? DEFAULT_BLOCK_CHUNK_THRESHOLD
114
- this.transferConcurrency = options.transferConcurrency ?? DEFAULT_TRANSFER_CONCURRENCY
115
- if (!Number.isSafeInteger(this.blockChunkSize) || this.blockChunkSize <= 0)
116
- throw new TypeError('blockChunkSize must be a positive integer')
117
- if (!Number.isSafeInteger(this.blockChunkThreshold) || this.blockChunkThreshold < 0)
118
- throw new TypeError('blockChunkThreshold must be a non-negative integer')
119
- if (!Number.isSafeInteger(this.transferConcurrency) || this.transferConcurrency <= 0)
120
- throw new TypeError('transferConcurrency must be a positive integer')
121
- const parts = this.network.split(':')
122
- this.networkVersion = options.networkVersion || parts.length > 1 ? parts[1] : 'mainnet'
123
-
124
- if (!options.storePrefix) options.storePrefix = 'lfc'
125
- if (!options.port) options.port = 2000
126
- if (!options.root) {
127
- parts[1] ? (options.root = `.${parts[0]}/${parts[1]}`) : (options.root = `.${this.network}`)
128
- }
129
-
130
- globalThis.peernet = this
131
- this.bw = {
132
- up: 0,
133
- down: 0
134
- }
135
- // @ts-ignore
136
- return this._init(options, password)
137
- }
138
-
139
- get id() {
140
- return this.identity.id
141
- }
142
-
143
- get selectedAccount(): string {
144
- return this.identity.selectedAccount
145
- }
146
-
147
- get accounts(): Promise<[[name: string, externalAddress: string, internalAddress: string]]> {
148
- return this.identity.accounts
149
- }
150
-
151
- get defaultStores() {
152
- return ['account', 'wallet', 'block', 'transaction', 'chain', 'data', 'message', 'share']
153
- }
154
-
155
- selectAccount(account: string) {
156
- return this.identity.selectAccount(account)
157
- }
158
-
159
- addProto(name, proto) {
160
- if (!globalThis.peernet.protos[name]) globalThis.peernet.protos[name] = proto
161
- }
162
-
163
- addCodec(codec) {
164
- return codecUtils.addCodec(codec)
165
- }
166
-
167
- async addStore(name, prefix, root, isPrivate = true) {
168
- if (
169
- name === 'block' ||
170
- name === 'transaction' ||
171
- name === 'chain' ||
172
- name === 'data' ||
173
- name === 'message' ||
174
- name === 'share'
175
- )
176
- isPrivate = false
177
-
178
- let Storage
179
-
180
- this.hasDaemon ? (Storage = LeofcoinStorageClient) : (Storage = LeofcoinStorage)
181
-
182
- if (!globalThis[`${name}Store`]) {
183
- globalThis[`${name}Store`] = new Storage(name, root)
184
- await globalThis[`${name}Store`].init()
185
- }
186
-
187
- globalThis[`${name}Store`].private = isPrivate
188
- if (!isPrivate) this.stores.push(name)
189
- }
190
-
191
- /**
192
- * @see MessageHandler
193
- */
194
- prepareMessage(data) {
195
- return this._messageHandler.prepareMessage(data)
196
- }
197
-
198
- /**
199
- * @access public
200
- *
201
- * @return {Array} peerId
202
- */
203
- get peers() {
204
- return Object.entries(this.client?.connections || {})
205
- }
206
-
207
- get connections() {
208
- return this.client?.connections || {}
209
- }
210
-
211
- /**
212
- * @return {String} id - peerId
213
- */
214
- getConnection(id) {
215
- return this.connections[id]
216
- }
217
-
218
- /**
219
- * @private
220
- *
221
- * @param {Object} options
222
- * @param {String} options.root - path to root directory
223
- *
224
- * @return {Promise} instance of Peernet
225
- */
226
- async _init(
227
- options: { storePrefix?: string; root?: string; freshIdentity?: boolean },
228
- password: string
229
- ): Promise<Peernet> {
230
- await getAddress()
231
- this.storePrefix = options.storePrefix
232
- this.root = options.root
233
-
234
- const {
235
- RequestMessage,
236
- ResponseMessage,
237
- PeerMessage,
238
- PeerMessageResponse,
239
- PeernetMessage,
240
- DHTMessage,
241
- DHTMessageResponse,
242
- DataMessage,
243
- DataMessageResponse,
244
- PsMessage,
245
- ChatMessage,
246
- PeernetFile
247
- } = await import(/* webpackChunkName: "messages" */ './messages.js')
248
-
249
- /**
250
- * proto Object containing protos
251
- * @type {Object}
252
- * @property {PeernetMessage} protos[peernet-message] messageNode
253
- * @property {DHTMessage} protos[peernet-dht] messageNode
254
- * @property {DHTMessageResponse} protos[peernet-dht-response] messageNode
255
- * @property {DataMessage} protos[peernet-data] messageNode
256
- * @property {DataMessageResponse} protos[peernet-data-response] messageNode
257
- */
258
-
259
- globalThis.peernet.protos = {
260
- 'peernet-request': RequestMessage,
261
- 'peernet-response': ResponseMessage,
262
- 'peernet-peer': PeerMessage,
263
- 'peernet-peer-response': PeerMessageResponse,
264
- 'peernet-message': PeernetMessage,
265
- 'peernet-dht': DHTMessage,
266
- 'peernet-dht-response': DHTMessageResponse,
267
- 'peernet-data': DataMessage,
268
- 'peernet-data-response': DataMessageResponse,
269
- 'peernet-ps': PsMessage,
270
- 'chat-message': ChatMessage,
271
- 'peernet-file': PeernetFile
272
- }
273
-
274
- this._messageHandler = new MessageHandler(this.network)
275
-
276
- const { daemon, environment } = await target()
277
- this.hasDaemon = daemon
278
-
279
- for (const store of this.defaultStores) {
280
- await this.addStore(store, options.storePrefix, options.root)
281
- }
282
-
283
- this.identity = new Identity(this.network)
284
- await this.identity.load(password, options.freshIdentity === true)
285
-
286
- this._peerHandler = new PeerDiscovery(this.id)
287
- this.peerId = this.id
288
-
289
- /**
290
- * converts data -> message -> proto
291
- * @see DataHandler
292
- */
293
- pubsub.subscribe('peer:data', dataHandler)
294
-
295
- if (this.autoStart) await this.start()
296
- return this
297
- }
298
-
299
- async start() {
300
- if (this.#starting || this.#started) return
301
-
302
- this.#starting = true
303
- const importee = await import('@netpeer/swarm/client')
304
- /**
305
- * @access public
306
- * @type {PeernetClient}
307
- */
308
- console.log(this.stars)
309
-
310
- this.client = new importee.default({
311
- peerId: this.id,
312
- networkVersion: this.networkVersion,
313
- version: this.version,
314
- stars: this.stars,
315
- transport: this.transport
316
- })
317
- this.#started = true
318
- this.#starting = false
319
- }
320
-
321
- // todo: remove, handled in swarm now
322
- // #peerLeft(peer: SwarmPeer) {
323
- // for (const [id, _peer] of Object.entries(this.connections)) {
324
- // if (_peer.id === peer.id && this.connections[id] && !this.connections[id].connected) {
325
- // delete this.connections[id]
326
- // this.removePeer(_peer)
327
- // }
328
- // }
329
- // }
330
-
331
- addRequestHandler(name, method) {
332
- this.requestProtos[name] = method
333
- }
334
-
335
- async sendMessage(peer, id, data) {
336
- if (peer.connected) {
337
- await peer.send(data, id)
338
- this.bw.up += data.length
339
- return id
340
- } else {
341
- return new Promise((resolve, reject) => {
342
- const onError = (error) => {
343
- this.removePeer(peer)
344
- reject(error)
345
- }
346
- peer.once('error', onError)
347
- peer.once('connect', async () => {
348
- if (!peer.connected) {
349
- peer.removeListener('error', onError)
350
- debug('Peer not connected')
351
- return
352
- }
353
- await peer.send(data, id)
354
- this.bw.up += data.length
355
- peer.removeListener('error', onError)
356
- resolve(id)
357
- })
358
- })
359
- }
360
- }
361
-
362
- async handleDHT(peer, id, proto) {
363
- let { hash, store } = proto.decoded
364
- let has
365
-
366
- if (store) {
367
- store = globalThis[`${store}Store`]
368
- has = store.private ? false : await store.has(hash)
369
- } else {
370
- has = await this.has(hash)
371
- }
372
-
373
- const data = await new globalThis.peernet.protos['peernet-dht-response']({
374
- hash,
375
- has
376
- })
377
- const node = await this.prepareMessage(data)
378
-
379
- this.sendMessage(peer, id, node.encoded)
380
- }
381
-
382
- /**
383
- * Broadcasts data to the network and returns a hash that can be used by another peer
384
- * to directly connect and download the data from the broadcasting peer.
385
- *
386
- * @param {Uint8Array|Buffer|Object|string} data - The data to broadcast
387
- * @param {string} [storeName='data'] - The store to use for storing the data
388
- * @returns {Promise<string>} The hash that can be shared for direct download
389
- */
390
- /**
391
- * Broadcasts data to the network and returns a hash that can be used by another peer
392
- * to directly connect and download the data from the broadcasting peer.
393
- * The data is kept in memory only and not persisted to storage.
394
- * @param {string} path - The path or identifier for the content being broadcasted
395
- * @param {{content?: Uint8Array, links?: any[]}} data - The data to broadcast
396
-
397
- * @returns {Promise<string>} The hash that can be shared for direct download
398
- */
399
- async broadcast(
400
- path: string,
401
- { content, links, chunkSize }: { content?: Uint8Array; links?: any[]; chunkSize?: number }
402
- ): Promise<string> {
403
- chunkSize = chunkSize ?? (content ? adaptiveChunkSize(content.length) : DEFAULT_CHUNK_SIZE)
404
- if (!Number.isSafeInteger(chunkSize) || chunkSize <= 0) throw new TypeError('chunkSize must be a positive integer')
405
- let protoInput: any
406
- if (content && content.length > chunkSize) {
407
- const chunkLinks = []
408
- for (let offset = 0, index = 0; offset < content.length; offset += chunkSize, index++) {
409
- const chunkNode = await new globalThis.peernet.protos['peernet-file']({
410
- path: `${path}.part-${index}`,
411
- content: content.slice(offset, Math.min(offset + chunkSize, content.length))
412
- })
413
- const chunkHash = await chunkNode.hash()
414
- const chunkEncoded = await chunkNode.encoded
415
- if (!this._inMemoryBroadcasts) this._inMemoryBroadcasts = new Map()
416
- this._inMemoryBroadcasts.set(chunkHash, chunkEncoded)
417
- await shareStore.put(chunkHash, chunkEncoded)
418
- chunkLinks.push({ hash: chunkHash, path: String(index), size: chunkNode.decoded.content.length })
419
- }
420
- protoInput = { path, links: chunkLinks, size: content.length, chunkSize, chunked: true }
421
- } else if (content) protoInput = { path, content, size: content.length }
422
- else if (links) protoInput = { path, links }
423
- else throw new TypeError('broadcast requires content or links')
424
-
425
- const protoNode = await new globalThis.peernet.protos['peernet-file'](protoInput)
426
- const hash = await protoNode.hash()
427
- const encoded = await protoNode.encoded
428
- if (!this._inMemoryBroadcasts) this._inMemoryBroadcasts = new Map()
429
- this._inMemoryBroadcasts.set(hash, encoded)
430
-
431
- // Persist to share store
432
- await shareStore.put(hash, encoded)
433
-
434
- await this.publish('broadcast', { hash, from: this.id })
435
- return hash
436
- }
437
-
438
- /** Create and immediately start a resumable, integrity-checked file download. */
439
- download(hash: string, options: { pin?: boolean; autoStart?: boolean } = {}): FileTransfer {
440
- const FileProto = globalThis.peernet.protos['peernet-file']
441
- const transfer = new FileTransfer({
442
- hash,
443
- fetch: async (wantedHash, index) => {
444
- if (wantedHash !== hash) {
445
- const providers = Object.values(this.dht.providersFor(hash) || {}) as DHTProvider[]
446
- for (const provider of providers) this.dht.addProvider(provider, wantedHash)
447
- }
448
- const store = await this.whichStore([...this.stores], wantedHash)
449
- if (store && (await store.has(wantedHash))) return store.get(wantedHash)
450
- const result = await this.requestData(wantedHash, undefined, { providerIndex: index })
451
- return result
452
- },
453
- decode: async (encoded) => {
454
- const node = await new FileProto(encoded)
455
- await node.decode()
456
- if (node.decoded?.chunked) {
457
- const providers = Object.values(this.dht.providersFor(hash) || {}) as DHTProvider[]
458
- for (const link of node.decoded.links || []) {
459
- for (const provider of providers) this.dht.addProvider(provider, link.hash)
460
- }
461
- }
462
- return node.decoded
463
- },
464
- concurrency: this.transferConcurrency,
465
- verify: async (encoded, expectedHash) => {
466
- const node = await new FileProto(encoded)
467
- return (await node.hash()) === expectedHash
468
- },
469
- pin: options.pin ? (chunkHash, encoded) => dataStore.put(chunkHash, encoded) : undefined
470
- })
471
- if (options.autoStart !== false) transfer.start()
472
- return transfer
473
- }
474
-
475
- async handleData(peer, id, proto) {
476
- let { hash, store } = proto.decoded
477
- let data
478
- try {
479
- if (this._inMemoryBroadcasts && this._inMemoryBroadcasts.has(hash)) {
480
- data = this._inMemoryBroadcasts.get(hash)
481
- let resolvedHash = hash
482
- if (typeof hash === 'function') {
483
- resolvedHash = await hash()
484
- }
485
- data = await new globalThis.peernet.protos['peernet-data-response']({
486
- hash: resolvedHash,
487
- data
488
- })
489
-
490
- const node = await this.prepareMessage(data)
491
- await this.sendMessage(peer, id, node.encoded)
492
- return
493
- }
494
-
495
- store = globalThis[`${store}Store`] || (await this.whichStore([...this.stores], hash))
496
-
497
- if (store && !store.private) {
498
- data = await store.get(hash)
499
-
500
- if (data) {
501
- data = await new globalThis.peernet.protos['peernet-data-response']({
502
- hash,
503
- data
504
- })
505
-
506
- const node = await this.prepareMessage(data)
507
- await this.sendMessage(peer, id, node.encoded)
508
- }
509
- } else {
510
- // ban (trying to access private st)
511
- }
512
- } catch (error) {
513
- console.error('handleData: error', error)
514
- return this.requestData(hash, store)
515
- }
516
- }
517
-
518
- async handleRequest(peer, id, proto) {
519
- const method = this.requestProtos[proto.decoded.request]
520
- if (method) {
521
- const data = await method(proto.decoded.requested)
522
- const node = await this.prepareMessage(data)
523
- this.sendMessage(peer, id, node.encoded)
524
- }
525
- }
526
-
527
- /**
528
- * Send a request to connected peers and return the first response.
529
- *
530
- * @param {String} requestName - name of the registered request handler on remote peers
531
- * @param {Uint8Array} [requested] - optional data to send with the request
532
- * @returns {Promise<Uint8Array|undefined>} decoded response data or undefined
533
- */
534
- async request(requestName: string, requested?: Uint8Array) {
535
- const input: { request: string; requested?: Uint8Array } = { request: requestName }
536
- if (requested) input.requested = requested
537
-
538
- const data = await new globalThis.peernet.protos['peernet-request'](input)
539
- const node = await this.prepareMessage(data)
540
-
541
- const requests: Promise<any>[] = []
542
- for (const [peerId, peer] of Object.entries(this.connections)) {
543
- if (peerId !== this.id && peer.connected) {
544
- requests.push(peer.request(node.encoded))
545
- }
546
- }
547
-
548
- if (requests.length === 0) return undefined
549
-
550
- try {
551
- let result = await Promise.any(requests)
552
- if (result) result = new Uint8Array(Object.values(result))
553
- if (!result || result.length === 0) return undefined
554
- const proto = await protoFor(result)
555
- return proto.decoded.response
556
- } catch (error) {
557
- debug('request failed', error)
558
- return undefined
559
- }
560
- }
561
-
562
- /**
563
- * @private
564
- *
565
- * @param {Buffer} message - peernet message
566
- * @param {PeernetPeer} peer - peernet peer
567
- */
568
- async _protoHandler(message, peer, from) {
569
- const { id, proto } = message
570
-
571
- this.bw.down += proto.encoded.length
572
- switch (proto.name) {
573
- case 'peernet-dht': {
574
- this.handleDHT(peer, id, proto)
575
- break
576
- }
577
- case 'peernet-data': {
578
- this.handleData(peer, id, proto)
579
- break
580
- }
581
- case 'peernet-request': {
582
- this.handleRequest(peer, id, proto)
583
- break
584
- }
585
-
586
- case 'peernet-ps': {
587
- globalSub.publish(new TextDecoder().decode(proto.decoded.topic), proto.decoded.data)
588
- }
589
- }
590
- }
591
-
592
- /**
593
- * performs a walk and resolves first encounter
594
- *
595
- * @param {String} hash
596
- */
597
- async walk(hash) {
598
- if (!hash) throw new Error('hash expected, received undefined')
599
- const data = await new globalThis.peernet.protos['peernet-dht']({ hash })
600
- const walk = async (peer, peerId) => {
601
- const node = await this.prepareMessage(data)
602
- try {
603
- let result = await peer.request(node.encoded)
604
- result = new Uint8Array(Object.values(result))
605
- const proto = await protoFor(result)
606
- if (proto.name !== 'peernet-dht-response') throw dhtError(proto.name)
607
-
608
- const peerInfo = {
609
- address: peer.remoteAddress,
610
- id: peerId
611
- }
612
-
613
- if (proto.decoded.has) this.dht.addProvider(peerInfo, proto.decoded.hash)
614
- } catch (error) {
615
- console.error(`Error while walking ${peerId}`, error)
616
- return undefined
617
- }
618
- }
619
- let walks = []
620
- for (const [peerId, peer] of Object.entries(this.connections)) {
621
- if (peerId !== this.id && peer.connected) {
622
- walks.push(walk(peer, peerId))
623
- }
624
- }
625
- // walk the network and return first result.
626
- // not waiting for all walks to finish, this is faster and more efficient
627
- return Promise.any(walks)
628
- }
629
-
630
- /**
631
- * Override DHT behavior, try's finding the content three times
632
- *
633
- * @param {String} hash
634
- */
635
- async providersFor(hash: string, store?: undefined) {
636
- let providers = this.dht.providersFor(hash)
637
- // walk the network to find a provider
638
- let tries = 0
639
- while ((!providers && tries < 3) || (providers && Object.keys(providers).length === 0 && tries < 3)) {
640
- tries += 1
641
- await this.walk(hash)
642
- providers = this.dht.providersFor(hash)
643
- }
644
- // undefined if no providers given
645
- return providers
646
- }
647
-
648
- get block() {
649
- return {
650
- get: async (hash: string) => {
651
- return this.#createBlockTransfer(hash).result
652
- },
653
- put: async (hash: string, data: Uint8Array) => {
654
- if (await blockStore.has(hash)) return
655
- if (data.length > this.blockChunkThreshold) return this.#putChunkedBlock(hash, data)
656
- return await blockStore.put(hash, data)
657
- },
658
- has: async (hash: string) => await blockStore.has(hash),
659
- download: (hash: string, options: { autoStart?: boolean } = {}) => this.#createBlockTransfer(hash, options)
660
- }
661
- }
662
-
663
- async #putChunkedBlock(hash: string, data: Uint8Array) {
664
- const links = []
665
- const FileProto = globalThis.peernet.protos['peernet-file']
666
- const chunkSize = adaptiveChunkSize(data.length, this.blockChunkSize)
667
- for (let offset = 0, index = 0; offset < data.length; offset += chunkSize, index++) {
668
- const content = data.slice(offset, Math.min(offset + chunkSize, data.length))
669
- const chunk = new FileProto({ path: `block-${hash}.part-${index}`, content })
670
- const chunkHash = await chunk.hash()
671
- await blockStore.put(chunkHash, chunk.encoded)
672
- links.push({ hash: chunkHash, path: String(index).padStart(12, '0'), size: content.length })
673
- }
674
-
675
- const manifest = new FileProto({
676
- path: `block-${hash}`,
677
- links,
678
- size: data.length,
679
- chunkSize,
680
- chunked: true,
681
- kind: 'block',
682
- blockHash: hash
683
- })
684
- return blockStore.put(hash, manifest.encoded)
685
- }
686
-
687
- #createBlockTransfer(hash: string, options: { autoStart?: boolean } = {}): FileTransfer {
688
- const FileProto = globalThis.peernet.protos['peernet-file']
689
- const fetch = async (wantedHash: string, index?: number) => {
690
- if (wantedHash !== hash) {
691
- const providers = Object.values(this.dht.providersFor(hash) || {}) as DHTProvider[]
692
- for (const provider of providers) this.dht.addProvider(provider, wantedHash)
693
- }
694
- if (await blockStore.has(wantedHash)) return blockStore.get(wantedHash)
695
- const result = await this.requestData(wantedHash, 'block', { providerIndex: index })
696
- return result
697
- }
698
- const transfer = new FileTransfer({
699
- hash,
700
- fetch,
701
- decode: async (encoded) => {
702
- let node
703
- try {
704
- node = new FileProto(encoded)
705
- await node.decode()
706
- } catch {
707
- // A normal block is intentionally not a peernet-file envelope.
708
- return { content: encoded }
709
- }
710
- if (node.decoded?.kind === 'block') {
711
- if (node.decoded.blockHash !== hash) throw new Error(`Block manifest hash mismatch for ${hash}`)
712
- const providers = Object.values(this.dht.providersFor(hash) || {}) as DHTProvider[]
713
- for (const link of node.decoded.links || []) {
714
- for (const provider of providers) this.dht.addProvider(provider, link.hash)
715
- }
716
- return node.decoded
717
- }
718
- if (node.decoded?.path?.startsWith(`block-${hash}.part-`)) return node.decoded
719
- return { content: encoded }
720
- },
721
- verify: async (encoded, expectedHash) => {
722
- const node = new FileProto(encoded)
723
- return (await node.hash()) === expectedHash
724
- },
725
- verifyManifest: false,
726
- concurrency: this.transferConcurrency,
727
- pin: (wantedHash, encoded) => blockStore.put(wantedHash, encoded)
728
- })
729
- if (options.autoStart !== false) transfer.start()
730
- return transfer
731
- }
732
-
733
- get transaction() {
734
- return {
735
- get: async (hash: string) => {
736
- const data = await transactionStore.has(hash)
737
- if (data) return await transactionStore.get(hash)
738
- return this.requestData(hash, 'transaction')
739
- },
740
- put: async (hash: string, data: Uint8Array) => {
741
- if (await transactionStore.has(hash)) return
742
- return await transactionStore.put(hash, data)
743
- },
744
- has: async (hash: string) => await transactionStore.has(hash)
745
- }
746
- }
747
-
748
- async requestData(hash, store, options: { providerIndex?: number } = {}) {
749
- try {
750
- const providers = await this.providersFor(hash)
751
- if (!providers || (providers && Object.keys(providers).length === 0)) throw nothingFoundError(hash)
752
- debug(`found ${Object.keys(providers).length} provider(s) for ${hash}`)
753
- // get closest peer on earth
754
- const providerValues = Object.values(providers) as DHTProvider[]
755
- let closestPeer: DHTProvider =
756
- options.providerIndex === undefined
757
- ? await this.dht.closestPeer(providerValues)
758
- : providerValues[options.providerIndex % providerValues.length]
759
- // fallback to first provider if no closest peer found
760
- if (!closestPeer || !closestPeer.id) closestPeer = Object.values(providers)[0]
761
-
762
- debug(`closest peer for ${hash} is ${closestPeer?.address}`)
763
- // get peer instance by id
764
- if (!closestPeer || !closestPeer.id) return undefined
765
- const id = closestPeer.id
766
- const peer = this.connections[id]
767
-
768
- if (!peer || !peer?.connected) {
769
- this.dht.removeProvider(id, hash)
770
- return this.requestData(hash, store?.name || store, options)
771
- }
772
-
773
- let data = await new globalThis.peernet.protos['peernet-data']({
774
- hash,
775
- store: store?.name || store
776
- })
777
-
778
- const node = await this.prepareMessage(data)
779
-
780
- if (peer?.connected) {
781
- try {
782
- if (peer) data = await peer.request(node.encoded)
783
- else {
784
- // fallback and try every provider found
785
- const promises = []
786
- const providers = await this.providersFor(hash, store)
787
- for (const provider of Object.values(providers)) {
788
- const peer = this.connections[provider.id]
789
-
790
- if (peer) promises.push(peer.request(node.encoded))
791
- }
792
- data = await Promise.race(promises)
793
- }
794
- if (data) data = new Uint8Array(Object.values(data))
795
- if (!data || data.length === 0) throw nothingFoundError(hash)
796
- const proto = await protoFor(data)
797
- // TODO: store data automaticly or not
798
- return BufferToUint8Array(proto.decoded.data)
799
- } catch (error) {
800
- debug(`Error while requesting data from ${id}`, error)
801
- // if error, remove provider
802
- if (this.#peerAttempts[id] > 1) {
803
- this.#peerAttempts[id] = 0
804
- debug(`Removed provider ${id} for ${hash} after 3 attempts`)
805
-
806
- this.dht.removeProvider(id, hash)
807
- console.error(nothingFoundError(hash))
808
- return undefined
809
- }
810
-
811
- if (this.#peerAttempts[id] === undefined) this.#peerAttempts[id] = 0
812
- this.#peerAttempts[id]++
813
- return this.requestData(hash, store?.name || store, options)
814
- }
815
-
816
- // this.put(hash, proto.decoded.data)
817
- } else {
818
- this.dht.removeProvider(id, hash)
819
- }
820
- return undefined
821
- } catch (error) {
822
- console.error(`Error while requesting data for ${hash} from the network`, error)
823
- return undefined
824
- }
825
- }
826
-
827
- get message() {
828
- return {
829
- /**
830
- * Get content for given message hash
831
- *
832
- * @param {String} hash
833
- */
834
- get: async (hash) => {
835
- debug(`get message ${hash}`)
836
- const message = await messageStore.has(hash)
837
- if (message) return await messageStore.get(hash)
838
- return this.requestData(hash, 'message')
839
- },
840
- /**
841
- * put message content
842
- *
843
- * @param {String} hash
844
- * @param {Buffer} message
845
- */
846
- put: async (hash, message) => await messageStore.put(hash, message),
847
- /**
848
- * @param {String} hash
849
- * @return {Boolean}
850
- */
851
- has: async (hash) => await messageStore.has(hash)
852
- }
853
- }
854
-
855
- get data() {
856
- return {
857
- /**
858
- * Get content for given data hash
859
- *
860
- * @param {String} hash
861
- */
862
- get: async (hash) => {
863
- debug(`get data ${hash}`)
864
- const data = await dataStore.has(hash)
865
- if (data) return await dataStore.get(hash)
866
- return this.requestData(hash, 'data')
867
- },
868
- /**
869
- * put data content
870
- *
871
- * @param {String} hash
872
- * @param {Buffer} data
873
- */
874
- put: async (hash, data) => await dataStore.put(hash, data),
875
- /**
876
- * @param {String} hash
877
- * @return {Boolean}
878
- */
879
- has: async (hash) => await dataStore.has(hash)
880
- }
881
- }
882
-
883
- get folder() {
884
- return {
885
- /**
886
- * Get content for given data hash
887
- *
888
- * @param {String} hash
889
- */
890
- get: async (hash) => {
891
- debug(`get data ${hash}`)
892
- const data = await dataStore.has(hash)
893
- if (data) return await dataStore.get(hash)
894
- return this.requestData(hash, 'data')
895
- },
896
- /**
897
- * put data content
898
- *
899
- * @param {String} hash
900
- * @param {Buffer} data
901
- */
902
- put: async (hash, data) => await dataStore.put(hash, data),
903
- /**
904
- * @param {String} hash
905
- * @return {Boolean}
906
- */
907
- has: async (hash) => await dataStore.has(hash)
908
- }
909
- }
910
-
911
- get share() {
912
- return {
913
- /**
914
- * Get content for given share hash
915
- *
916
- * @todo Add peer permission checking to validate if requesting peer has access
917
- *
918
- * @param {String} hash
919
- */
920
- get: async (hash) => {
921
- debug(`get share ${hash}`)
922
- const data = await shareStore.has(hash)
923
- if (data) return await shareStore.get(hash)
924
- return this.requestData(hash, 'share')
925
- },
926
- /**
927
- * put share content
928
- *
929
- * @param {String} hash
930
- * @param {Buffer} data
931
- */
932
- put: async (hash, data) => await shareStore.put(hash, data),
933
- /**
934
- * @param {String} hash
935
- * @return {Boolean}
936
- */
937
- has: async (hash) => await shareStore.has(hash),
938
-
939
- /**
940
- * Put folder content
941
- *
942
- * @param {Array} files
943
- */
944
- putFolder: async (files) => await this.addFolder(files)
945
- }
946
- }
947
-
948
- /**
949
- * Get all shared hashes
950
- *
951
- * @returns {Promise<string[]>} Array of all shared hashes
952
- */
953
- async allSharedHashes(): Promise<string[]> {
954
- return await shareStore.keys()
955
- }
956
-
957
- async addFolder(files) {
958
- const processFile = async (file) => {
959
- const fileNode = await new globalThis.peernet.protos['peernet-file'](file)
960
- const hash = await fileNode.hash()
961
- await dataStore.put(hash, fileNode.encoded)
962
- return { hash, path: file.path }
963
- }
964
-
965
- const links = await Promise.all(files.map(processFile))
966
-
967
- const node = await new globalThis.peernet.protos['peernet-file']({
968
- path: '/',
969
- links
970
- })
971
- const hash = await node.hash()
972
- await dataStore.put(hash, node.encoded)
973
-
974
- return hash
975
- }
976
-
977
- async ls(hash, options) {
978
- let data
979
- const has = await dataStore.has(hash)
980
- data = has ? await dataStore.get(hash) : await this.requestData(hash, 'data')
981
- if (!data) throw nothingFoundError(hash)
982
-
983
- const node = await new globalThis.peernet.protos['peernet-file'](data)
984
- await node.decode()
985
- const paths = []
986
- if (node.decoded?.links.length === 0) throw new Error(`${hash} is a file`)
987
- for (const { path, hash } of node.decoded.links) {
988
- paths.push({ path, hash })
989
- }
990
- if (options?.pin) await dataStore.put(hash, node.encoded)
991
- return paths
992
- }
993
-
994
- async cat(hash, options) {
995
- const transfer = this.download(hash, { pin: options?.pin })
996
- return transfer.result
997
- }
998
-
999
- /**
1000
- * goes trough given stores and tries to find data for given hash
1001
- * @param {Array} stores
1002
- * @param {string} hash
1003
- */
1004
- async whichStore(stores: string[], hash: string) {
1005
- const checkStore = async (name) => {
1006
- const store = globalThis[`${name}Store`]
1007
- if (store) {
1008
- const has = await store.has(hash)
1009
- if (has) return store
1010
- }
1011
- throw new Error('Not found')
1012
- }
1013
-
1014
- try {
1015
- return await Promise.any(stores.map(checkStore))
1016
- } catch {
1017
- return undefined
1018
- }
1019
- }
1020
-
1021
- /**
1022
- * Get content for given hash
1023
- *
1024
- * @param {String} hash - the hash of the wanted data
1025
- * @param {String} store - storeName to access
1026
- */
1027
- async get(hash, store) {
1028
- debug(`get ${hash}`)
1029
- let data
1030
- if (store) store = globalThis[`${store}Store`]
1031
- if (!store) store = await this.whichStore([...this.stores], hash)
1032
- if (store && (await store.has(hash))) data = await store.get(hash)
1033
- if (data) return data
1034
-
1035
- return this.requestData(hash, store?.name || store)
1036
- }
1037
-
1038
- /**
1039
- * put content
1040
- *
1041
- * @param {String} hash
1042
- * @param {Buffer} data
1043
- * @param {String} storeName - storeName to access
1044
- */
1045
- async put(hash: string, data: Uint8Array, storeName: string | LeofcoinStorageClass = 'data') {
1046
- const store: LeofcoinStorageClass = globalThis[`${storeName}Store`]
1047
- return store.put(hash, data)
1048
- }
1049
-
1050
- /**
1051
- * @param {String} hash
1052
- * @return {Boolean}
1053
- */
1054
- async has(hash) {
1055
- const store = await this.whichStore([...this.stores], hash)
1056
- if (store) {
1057
- return store.private ? false : true
1058
- }
1059
- return false
1060
- }
1061
-
1062
- /**
1063
- *
1064
- * @param {String} topic
1065
- * @param {String|Object|Array|Boolean|Buffer} data
1066
- */
1067
- async publish(topic, data) {
1068
- // globalSub.publish(topic, data)
1069
- const id = Math.random().toString(36).slice(-12)
1070
- data = await new globalThis.peernet.protos['peernet-ps']({ data, topic })
1071
- for (const [peerId, peer] of Object.entries(this.connections)) {
1072
- if (peerId !== this.id) {
1073
- const node = await this.prepareMessage(data)
1074
- this.sendMessage(peer, id, node.encoded)
1075
- }
1076
- // TODO: if peer subscribed
1077
- }
1078
- }
1079
-
1080
- // createHash(data, name) {
1081
- // return new CodeHash(data, {name})
1082
- // }
1083
-
1084
- /**
1085
- *
1086
- * @param {String} topic
1087
- * @param {Method} cb
1088
- */
1089
- async subscribe(topic: string, callback: Handler) {
1090
- // TODO: if peer subscribed
1091
- globalSub.subscribe(topic, callback)
1092
- }
1093
-
1094
- async removePeer(peer) {
1095
- console.log('removepeer', peer.id)
1096
- const id = peer.id
1097
- // await this.client.connections(peer)
1098
- // if (this.client.peers[id]) {
1099
- // for (const connection of Object.keys(this.client.peers[id])) {
1100
- // // if (this.client.peers[id][connection].connected === false) delete this.client.peers[id][connection]
1101
- // // @ts-ignore
1102
- // if (this.client.peers[id][connection].connected) return this.client.emit('peerconnect', connection)
1103
- // }
1104
- // }
1105
- }
1106
-
1107
- get Buffer() {
1108
- return Buffer
1109
- }
1110
- }
1111
-
1112
- globalThis.Peernet = Peernet
1113
- export { FileTransfer }