@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.
- package/docs/automatic-transport.md +37 -0
- package/exports/browser/{client-CAC7RVMf.js → client-C_pBoi1g.js} +252 -76
- package/exports/browser/{messages-Dw86z0zq.js → messages-BGHaihSi.js} +1 -1
- package/exports/browser/{peernet-B2jRhqGI.js → peernet-Db43ag2D.js} +2 -2
- package/exports/browser/peernet.js +1 -1
- package/exports/store.js +0 -3
- package/package.json +8 -4
- package/.esdoc.json +0 -10
- package/.gitattributes +0 -2
- package/.github/workflows/release.yml +0 -34
- package/.prettierrc +0 -7
- package/.travis.yml +0 -27
- package/BREAKING_CHANGES.md +0 -35
- package/deploy.js +0 -8
- package/exports/browser/browser-D-r0O9Qn.js +0 -53
- package/exports/browser/browser-DvU1xNFS.js +0 -147
- package/exports/browser/browser-_hiyXwPp.js +0 -147
- package/exports/browser/client-86Tnax66.js +0 -2224
- package/exports/browser/client-BFxJts6j.js +0 -2224
- package/exports/browser/client-BVhUamQG.js +0 -2200
- package/exports/browser/client-BzvWzzLv.js +0 -2224
- package/exports/browser/client-C9GBz2Tf.js +0 -2224
- package/exports/browser/client-CQzuu6At.js +0 -2224
- package/exports/browser/client-CVkFejdx.js +0 -2224
- package/exports/browser/client-Cj_UUO8W.js +0 -2224
- package/exports/browser/client-CrGaeBjj.js +0 -2224
- package/exports/browser/client-DDC8C510.js +0 -2224
- package/exports/browser/client-ERocgKET.js +0 -2224
- package/exports/browser/client-a8rws3cu.js +0 -2224
- package/exports/browser/identity-CYVPDmkf.js +0 -8402
- package/exports/browser/identity-ChyyZSve.js +0 -8397
- package/exports/browser/index-BD0Anx7_.js +0 -37
- package/exports/browser/index-Bxr5Iztg.js +0 -37
- package/exports/browser/messages-BFCmg6Vb.js +0 -212
- package/exports/browser/messages-BHIC2mQu.js +0 -212
- package/exports/browser/messages-CimzJ4wr.js +0 -210
- package/exports/browser/messages-Cjoa_LGO.js +0 -210
- package/exports/browser/messages-D7JxkkDW.js +0 -212
- package/exports/browser/messages-DH0tgNM9.js +0 -207
- package/exports/browser/messages-DVHcNvs_.js +0 -212
- package/exports/browser/messages-DqL_g2qw.js +0 -210
- package/exports/browser/messages-UKnuelZ7.js +0 -207
- package/exports/browser/messages-XgZZUMma.js +0 -212
- package/exports/browser/messages-kfFyctPL.js +0 -210
- package/exports/browser/messages-psROnfRi.js +0 -212
- package/exports/browser/peernet-B9VL4Xwe.js +0 -9522
- package/exports/browser/peernet-C-HNdrcd.js +0 -9528
- package/exports/browser/peernet-C4rOvyyU.js +0 -9661
- package/exports/browser/peernet-Ca1iM2tN.js +0 -9336
- package/exports/browser/peernet-CbI0-jFn.js +0 -9651
- package/exports/browser/peernet-CtHScDyh.js +0 -9524
- package/exports/browser/peernet-DfuxUK3u.js +0 -9525
- package/exports/browser/peernet-DiEcNDLD.js +0 -9655
- package/exports/browser/peernet-DtuRXOzP.js +0 -9653
- package/exports/browser/peernet-r_9IyzTe.js +0 -9336
- package/exports/browser/peernet-rcD5XL1y.js +0 -9590
- package/exports/browser/peernet-rw8HJGOz.js +0 -9597
- package/exports/messages-CHKVVTVE.js +0 -204
- package/exports/messages-DaUtFfBh.js +0 -207
- package/index.html +0 -19
- package/rollup.config.js +0 -143
- package/src/dht/dht.ts +0 -142
- package/src/discovery/peer-discovery.js +0 -75
- package/src/errors/errors.js +0 -12
- package/src/file-transfer.ts +0 -194
- package/src/handlers/data.js +0 -15
- package/src/handlers/message.js +0 -34
- package/src/identity.ts +0 -172
- package/src/messages/chat.ts +0 -13
- package/src/messages/data-response.ts +0 -13
- package/src/messages/data.ts +0 -17
- package/src/messages/dht-response.ts +0 -13
- package/src/messages/dht.ts +0 -21
- package/src/messages/file-link.ts +0 -17
- package/src/messages/file.ts +0 -17
- package/src/messages/peer-response.ts +0 -13
- package/src/messages/peer.ts +0 -13
- package/src/messages/peernet.ts +0 -13
- package/src/messages/ps.ts +0 -13
- package/src/messages/request.ts +0 -13
- package/src/messages/response.ts +0 -13
- package/src/messages.js +0 -12
- package/src/peer-info.js +0 -9
- package/src/peernet.ts +0 -1113
- package/src/prompts/password/browser.js +0 -1
- package/src/prompts/password/node.js +0 -6
- package/src/prompts/password.js +0 -1
- package/src/proto/chat-message.proto.js +0 -6
- package/src/proto/data-response.proto.js +0 -4
- package/src/proto/data.proto.js +0 -4
- package/src/proto/dht-response.proto.js +0 -4
- package/src/proto/dht.proto.js +0 -4
- package/src/proto/file-link.proto.js +0 -5
- package/src/proto/file.proto.js +0 -10
- package/src/proto/peer-response.proto.js +0 -3
- package/src/proto/peer.proto.js +0 -3
- package/src/proto/peernet.proto.js +0 -7
- package/src/proto/ps.proto.js +0 -4
- package/src/proto/request.proto.js +0 -4
- package/src/proto/response.proto.js +0 -3
- package/src/types.ts +0 -25
- package/src/utils/utils.js +0 -77
- package/test/all.js +0 -5
- package/test/client.js +0 -15
- package/test/codec.js +0 -56
- package/test/file-transfer.test.js +0 -81
- package/test/fresh-empty-store.js +0 -15
- package/test/hash.js +0 -14
- package/test/index.js +0 -6
- package/test/lastBlock.js +0 -7
- package/test/messages.js +0 -26
- package/test/peer-data-exchange.test.js +0 -52
- package/test/peer-file-exchange.test.js +0 -78
- package/test/peernet.js +0 -17
- package/test/peernet.test.js +0 -477
- package/test-output.log +0 -84
- package/test.js +0 -63
- package/test2.js +0 -13
- package/test3.js +0 -16
- package/test4.js +0 -7
- 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 }
|