hypercore 10.0.0-alpha.3 → 10.0.0-alpha.32
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/README.md +45 -3
- package/index.js +390 -156
- package/lib/bitfield.js +9 -5
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/caps.js +34 -0
- package/lib/core.js +32 -13
- package/lib/merkle-tree.js +184 -109
- package/lib/messages.js +245 -167
- package/lib/oplog.js +4 -3
- package/lib/replicator.js +1359 -593
- package/lib/streams.js +56 -0
- package/package.json +17 -8
- package/.github/workflows/test-node.yml +0 -24
- package/UPGRADE.md +0 -9
- package/examples/announce.js +0 -19
- package/examples/basic.js +0 -10
- package/examples/http.js +0 -123
- package/examples/lookup.js +0 -20
- package/lib/extensions.js +0 -76
- package/lib/protocol.js +0 -522
- package/lib/random-iterator.js +0 -46
- package/test/basic.js +0 -78
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- package/test/extension.js +0 -71
- package/test/helpers/index.js +0 -23
- package/test/merkle-tree.js +0 -518
- package/test/mutex.js +0 -137
- package/test/oplog.js +0 -399
- package/test/preload.js +0 -72
- package/test/replicate.js +0 -296
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
package/index.js
CHANGED
|
@@ -3,14 +3,18 @@ const raf = require('random-access-file')
|
|
|
3
3
|
const isOptions = require('is-options')
|
|
4
4
|
const hypercoreCrypto = require('hypercore-crypto')
|
|
5
5
|
const c = require('compact-encoding')
|
|
6
|
+
const b4a = require('b4a')
|
|
7
|
+
const Xache = require('xache')
|
|
6
8
|
const NoiseSecretStream = require('@hyperswarm/secret-stream')
|
|
9
|
+
const Protomux = require('protomux')
|
|
7
10
|
const codecs = require('codecs')
|
|
8
11
|
|
|
9
12
|
const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
10
13
|
|
|
11
14
|
const Replicator = require('./lib/replicator')
|
|
12
|
-
const Extensions = require('./lib/extensions')
|
|
13
15
|
const Core = require('./lib/core')
|
|
16
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
17
|
+
const { ReadStream, WriteStream } = require('./lib/streams')
|
|
14
18
|
|
|
15
19
|
const promises = Symbol.for('hypercore.promises')
|
|
16
20
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -27,14 +31,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
27
31
|
opts = key
|
|
28
32
|
key = null
|
|
29
33
|
}
|
|
34
|
+
|
|
30
35
|
if (key && typeof key === 'string') {
|
|
31
|
-
key =
|
|
36
|
+
key = b4a.from(key, 'hex')
|
|
32
37
|
}
|
|
33
|
-
|
|
38
|
+
|
|
39
|
+
if (!opts) opts = {}
|
|
40
|
+
|
|
41
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
34
42
|
throw new Error('Hypercore key should be 32 bytes')
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
if (!opts) opts = {}
|
|
38
45
|
if (!storage) storage = opts.storage
|
|
39
46
|
|
|
40
47
|
this[promises] = true
|
|
@@ -43,11 +50,16 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
43
50
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
44
51
|
this.core = null
|
|
45
52
|
this.replicator = null
|
|
46
|
-
this.
|
|
53
|
+
this.encryption = null
|
|
54
|
+
this.extensions = new Map()
|
|
55
|
+
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
47
56
|
|
|
48
57
|
this.valueEncoding = null
|
|
58
|
+
this.encodeBatch = null
|
|
59
|
+
this.activeRequests = []
|
|
60
|
+
|
|
49
61
|
this.key = key || null
|
|
50
|
-
this.
|
|
62
|
+
this.keyPair = null
|
|
51
63
|
this.readable = true
|
|
52
64
|
this.writable = false
|
|
53
65
|
this.opened = false
|
|
@@ -57,8 +69,11 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
57
69
|
this.autoClose = !!opts.autoClose
|
|
58
70
|
|
|
59
71
|
this.closing = null
|
|
60
|
-
this.opening =
|
|
72
|
+
this.opening = this._openSession(key, storage, opts)
|
|
61
73
|
this.opening.catch(noop)
|
|
74
|
+
|
|
75
|
+
this._preappend = preappend.bind(this)
|
|
76
|
+
this._snapshot = opts.snapshot || null
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
[inspect] (depth, opts) {
|
|
@@ -79,9 +94,41 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
79
94
|
indent + ')'
|
|
80
95
|
}
|
|
81
96
|
|
|
82
|
-
static
|
|
83
|
-
|
|
84
|
-
|
|
97
|
+
static getProtocolMuxer (stream) {
|
|
98
|
+
return stream.noiseStream.userData
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static createProtocolStream (isInitiator, opts = {}) {
|
|
102
|
+
let outerStream = Protomux.isProtomux(isInitiator)
|
|
103
|
+
? isInitiator.stream
|
|
104
|
+
: isStream(isInitiator)
|
|
105
|
+
? isInitiator
|
|
106
|
+
: opts.stream
|
|
107
|
+
|
|
108
|
+
let noiseStream = null
|
|
109
|
+
|
|
110
|
+
if (outerStream) {
|
|
111
|
+
noiseStream = outerStream.noiseStream
|
|
112
|
+
} else {
|
|
113
|
+
noiseStream = new NoiseSecretStream(isInitiator, null, opts)
|
|
114
|
+
outerStream = noiseStream.rawStream
|
|
115
|
+
}
|
|
116
|
+
if (!noiseStream) throw new Error('Invalid stream')
|
|
117
|
+
|
|
118
|
+
if (!noiseStream.userData) {
|
|
119
|
+
const protocol = new Protomux(noiseStream)
|
|
120
|
+
|
|
121
|
+
if (opts.ondiscoverykey) {
|
|
122
|
+
protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
|
|
123
|
+
}
|
|
124
|
+
if (opts.keepAlive !== false) {
|
|
125
|
+
noiseStream.setKeepAlive(5000)
|
|
126
|
+
noiseStream.setTimeout(7000)
|
|
127
|
+
}
|
|
128
|
+
noiseStream.userData = protocol
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return outerStream
|
|
85
132
|
}
|
|
86
133
|
|
|
87
134
|
static defaultStorage (storage, opts = {}) {
|
|
@@ -96,6 +143,10 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
96
143
|
}
|
|
97
144
|
}
|
|
98
145
|
|
|
146
|
+
snapshot () {
|
|
147
|
+
return this.session({ snapshot: { length: this.length, byteLength: this.byteLength, fork: this.fork } })
|
|
148
|
+
}
|
|
149
|
+
|
|
99
150
|
session (opts = {}) {
|
|
100
151
|
if (this.closing) {
|
|
101
152
|
// This makes the closing logic alot easier. If this turns out to be a problem
|
|
@@ -104,39 +155,124 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
104
155
|
}
|
|
105
156
|
|
|
106
157
|
const Clz = opts.class || Hypercore
|
|
107
|
-
const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
|
|
108
|
-
|
|
109
|
-
// This only works if the hypercore was fully loaded,
|
|
110
|
-
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
111
|
-
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
112
|
-
|
|
113
158
|
const s = new Clz(this.storage, this.key, {
|
|
114
159
|
...opts,
|
|
115
|
-
sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
|
|
116
|
-
valueEncoding: this.valueEncoding,
|
|
117
|
-
extensions: this.extensions,
|
|
118
160
|
_opening: this.opening,
|
|
119
161
|
_sessions: this.sessions
|
|
120
162
|
})
|
|
121
163
|
|
|
122
|
-
s.
|
|
164
|
+
s._passCapabilities(this)
|
|
123
165
|
this.sessions.push(s)
|
|
124
166
|
|
|
125
167
|
return s
|
|
126
168
|
}
|
|
127
169
|
|
|
128
|
-
|
|
170
|
+
_passCapabilities (o) {
|
|
129
171
|
if (!this.sign) this.sign = o.sign
|
|
130
172
|
this.crypto = o.crypto
|
|
131
|
-
this.opened = o.opened
|
|
132
173
|
this.key = o.key
|
|
133
|
-
this.discoveryKey = o.discoveryKey
|
|
134
174
|
this.core = o.core
|
|
135
175
|
this.replicator = o.replicator
|
|
176
|
+
this.encryption = o.encryption
|
|
136
177
|
this.writable = !!this.sign
|
|
137
178
|
this.autoClose = o.autoClose
|
|
138
179
|
}
|
|
139
180
|
|
|
181
|
+
async _openFromExisting (from, opts) {
|
|
182
|
+
await from.opening
|
|
183
|
+
|
|
184
|
+
this._passCapabilities(from)
|
|
185
|
+
this.sessions = from.sessions
|
|
186
|
+
this.storage = from.storage
|
|
187
|
+
|
|
188
|
+
this.sessions.push(this)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async _openSession (key, storage, opts) {
|
|
192
|
+
const isFirst = !opts._opening
|
|
193
|
+
|
|
194
|
+
if (!isFirst) await opts._opening
|
|
195
|
+
if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
|
|
196
|
+
|
|
197
|
+
const keyPair = (key && opts.keyPair)
|
|
198
|
+
? { ...opts.keyPair, publicKey: key }
|
|
199
|
+
: key
|
|
200
|
+
? { publicKey: key, secretKey: null }
|
|
201
|
+
: opts.keyPair
|
|
202
|
+
|
|
203
|
+
// This only works if the hypercore was fully loaded,
|
|
204
|
+
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
205
|
+
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
206
|
+
|
|
207
|
+
if (opts.sign) {
|
|
208
|
+
this.sign = opts.sign
|
|
209
|
+
} else if (keyPair && keyPair.secretKey) {
|
|
210
|
+
this.sign = Core.createSigner(this.crypto, keyPair)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (isFirst) {
|
|
214
|
+
await this._openCapabilities(keyPair, storage, opts)
|
|
215
|
+
// Only the root session should pass capabilities to other sessions.
|
|
216
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
217
|
+
const s = this.sessions[i]
|
|
218
|
+
if (s !== this) s._passCapabilities(this)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!this.sign) this.sign = this.core.defaultSign
|
|
223
|
+
this.writable = !!this.sign
|
|
224
|
+
|
|
225
|
+
if (opts.valueEncoding) {
|
|
226
|
+
this.valueEncoding = c.from(codecs(opts.valueEncoding))
|
|
227
|
+
}
|
|
228
|
+
if (opts.encodeBatch) {
|
|
229
|
+
this.encodeBatch = opts.encodeBatch
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// This is a hidden option that's only used by Corestore.
|
|
233
|
+
// It's required so that corestore can load a name from userData before 'ready' is emitted.
|
|
234
|
+
if (opts._preready) await opts._preready(this)
|
|
235
|
+
|
|
236
|
+
this.opened = true
|
|
237
|
+
this.emit('ready')
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async _openCapabilities (keyPair, storage, opts) {
|
|
241
|
+
if (opts.from) return this._openFromExisting(opts.from, opts)
|
|
242
|
+
|
|
243
|
+
this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
244
|
+
|
|
245
|
+
this.core = await Core.open(this.storage, {
|
|
246
|
+
force: opts.force,
|
|
247
|
+
createIfMissing: opts.createIfMissing,
|
|
248
|
+
overwrite: opts.overwrite,
|
|
249
|
+
keyPair,
|
|
250
|
+
crypto: this.crypto,
|
|
251
|
+
legacy: opts.legacy,
|
|
252
|
+
onupdate: this._oncoreupdate.bind(this)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
if (opts.userData) {
|
|
256
|
+
for (const [key, value] of Object.entries(opts.userData)) {
|
|
257
|
+
await this.core.userData(key, value)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.key = this.core.header.signer.publicKey
|
|
262
|
+
this.keyPair = this.core.header.signer
|
|
263
|
+
|
|
264
|
+
this.replicator = new Replicator(this.core, this.key, {
|
|
265
|
+
eagerUpdate: true,
|
|
266
|
+
allowFork: opts.allowFork !== false,
|
|
267
|
+
onpeerupdate: this._onpeerupdate.bind(this),
|
|
268
|
+
onupload: this._onupload.bind(this)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
272
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
140
276
|
close () {
|
|
141
277
|
if (this.closing) return this.closing
|
|
142
278
|
this.closing = this._close()
|
|
@@ -153,6 +289,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
153
289
|
this.readable = false
|
|
154
290
|
this.writable = false
|
|
155
291
|
this.closed = true
|
|
292
|
+
this.opened = false
|
|
293
|
+
|
|
294
|
+
const gc = []
|
|
295
|
+
for (const ext of this.extensions.values()) {
|
|
296
|
+
if (ext.session === this) gc.push(ext)
|
|
297
|
+
}
|
|
298
|
+
for (const ext of gc) ext.destroy()
|
|
299
|
+
|
|
300
|
+
if (this.replicator !== null) {
|
|
301
|
+
this.replicator.clearRequests(this.activeRequests)
|
|
302
|
+
}
|
|
156
303
|
|
|
157
304
|
if (this.sessions.length) {
|
|
158
305
|
// if this is the last session and we are auto closing, trigger that first to enforce error handling
|
|
@@ -168,145 +315,104 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
168
315
|
}
|
|
169
316
|
|
|
170
317
|
replicate (isInitiator, opts = {}) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: opts.stream
|
|
174
|
-
let noiseStream = null
|
|
175
|
-
|
|
176
|
-
if (outerStream) {
|
|
177
|
-
noiseStream = outerStream.noiseStream
|
|
178
|
-
} else {
|
|
179
|
-
outerStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
180
|
-
noiseStream = outerStream.noiseStream
|
|
181
|
-
}
|
|
182
|
-
if (!noiseStream) throw new Error('Invalid stream passed to replicate')
|
|
183
|
-
|
|
184
|
-
if (!noiseStream.userData) {
|
|
185
|
-
const protocol = Replicator.createProtocol(noiseStream)
|
|
186
|
-
noiseStream.userData = protocol
|
|
187
|
-
noiseStream.on('error', noop) // All noise errors already propagate through outerStream
|
|
188
|
-
}
|
|
189
|
-
|
|
318
|
+
const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
319
|
+
const noiseStream = protocolStream.noiseStream
|
|
190
320
|
const protocol = noiseStream.userData
|
|
321
|
+
|
|
191
322
|
if (this.opened) {
|
|
192
|
-
this.replicator.
|
|
323
|
+
this.replicator.attachTo(protocol)
|
|
193
324
|
} else {
|
|
194
|
-
this.opening.then(() => this.replicator.
|
|
325
|
+
this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
|
|
195
326
|
}
|
|
196
327
|
|
|
197
|
-
return
|
|
328
|
+
return protocolStream
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
get discoveryKey () {
|
|
332
|
+
return this.replicator === null ? null : this.replicator.discoveryKey
|
|
198
333
|
}
|
|
199
334
|
|
|
200
335
|
get length () {
|
|
201
|
-
return this.
|
|
336
|
+
return this._snapshot
|
|
337
|
+
? this._snapshot.length
|
|
338
|
+
: (this.core === null ? 0 : this.core.tree.length)
|
|
202
339
|
}
|
|
203
340
|
|
|
204
341
|
get byteLength () {
|
|
205
|
-
return this.
|
|
342
|
+
return this._snapshot
|
|
343
|
+
? this._snapshot.byteLength
|
|
344
|
+
: (this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding))
|
|
206
345
|
}
|
|
207
346
|
|
|
208
347
|
get fork () {
|
|
209
|
-
return this.
|
|
348
|
+
return this._snapshot
|
|
349
|
+
? this._snapshot.fork
|
|
350
|
+
: (this.core === null ? 0 : this.core.tree.fork)
|
|
210
351
|
}
|
|
211
352
|
|
|
212
353
|
get peers () {
|
|
213
354
|
return this.replicator === null ? [] : this.replicator.peers
|
|
214
355
|
}
|
|
215
356
|
|
|
216
|
-
|
|
217
|
-
return this.
|
|
357
|
+
get encryptionKey () {
|
|
358
|
+
return this.encryption && this.encryption.key
|
|
218
359
|
}
|
|
219
360
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
|
|
224
|
-
|
|
225
|
-
const keyPair = (key && opts.keyPair)
|
|
226
|
-
? { ...opts.keyPair, publicKey: key }
|
|
227
|
-
: key
|
|
228
|
-
? { publicKey: key, secretKey: null }
|
|
229
|
-
: opts.keyPair
|
|
230
|
-
|
|
231
|
-
if (opts.from) {
|
|
232
|
-
const from = opts.from
|
|
233
|
-
await from.opening
|
|
234
|
-
for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
|
|
235
|
-
this._initSession(from)
|
|
236
|
-
this.extensions = from.extensions
|
|
237
|
-
this.sessions = from.sessions
|
|
238
|
-
this.storage = from.storage
|
|
239
|
-
if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
|
|
240
|
-
this.writable = !!this.sign
|
|
241
|
-
this.sessions.push(this)
|
|
242
|
-
return
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
246
|
-
|
|
247
|
-
this.core = await Core.open(this.storage, {
|
|
248
|
-
keyPair,
|
|
249
|
-
crypto: this.crypto,
|
|
250
|
-
onupdate: this._oncoreupdate.bind(this)
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
if (opts.userData) {
|
|
254
|
-
for (const [key, value] of Object.entries(opts.userData)) {
|
|
255
|
-
await this.core.userData(key, value)
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
this.replicator = new Replicator(this.core, {
|
|
260
|
-
onupdate: this._onpeerupdate.bind(this)
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
if (!this.sign) this.sign = opts.sign || this.core.defaultSign
|
|
264
|
-
|
|
265
|
-
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
266
|
-
this.key = this.core.header.signer.publicKey
|
|
267
|
-
this.writable = !!this.sign
|
|
361
|
+
get padding () {
|
|
362
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
363
|
+
}
|
|
268
364
|
|
|
269
|
-
|
|
270
|
-
this.
|
|
365
|
+
ready () {
|
|
366
|
+
return this.opening
|
|
367
|
+
}
|
|
271
368
|
|
|
272
|
-
|
|
369
|
+
_onupload (index, value, from) {
|
|
370
|
+
const byteLength = value.byteLength - this.padding
|
|
273
371
|
|
|
274
372
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
275
|
-
|
|
276
|
-
if (s !== this) s._initSession(this)
|
|
277
|
-
s.emit('ready')
|
|
373
|
+
this.sessions[i].emit('upload', index, byteLength, from)
|
|
278
374
|
}
|
|
279
375
|
}
|
|
280
376
|
|
|
281
377
|
_oncoreupdate (status, bitfield, value, from) {
|
|
282
378
|
if (status !== 0) {
|
|
283
379
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
284
|
-
if ((status & 0b10) !== 0)
|
|
285
|
-
|
|
380
|
+
if ((status & 0b10) !== 0) {
|
|
381
|
+
if (this.cache) this.cache.clear()
|
|
382
|
+
this.sessions[i].emit('truncate', bitfield.start, this.core.tree.fork)
|
|
383
|
+
}
|
|
384
|
+
if ((status & 0b01) !== 0) {
|
|
385
|
+
this.sessions[i].emit('append')
|
|
386
|
+
}
|
|
286
387
|
}
|
|
287
388
|
|
|
288
|
-
this.replicator.
|
|
389
|
+
this.replicator.localUpgrade()
|
|
289
390
|
}
|
|
290
391
|
|
|
291
|
-
if (bitfield
|
|
292
|
-
|
|
293
|
-
this.replicator.broadcastBlock(bitfield.start + i)
|
|
294
|
-
}
|
|
392
|
+
if (bitfield) {
|
|
393
|
+
this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
|
|
295
394
|
}
|
|
296
395
|
|
|
297
396
|
if (value) {
|
|
397
|
+
const byteLength = value.byteLength - this.padding
|
|
398
|
+
|
|
298
399
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
299
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
400
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
300
401
|
}
|
|
301
402
|
}
|
|
302
403
|
}
|
|
303
404
|
|
|
304
405
|
_onpeerupdate (added, peer) {
|
|
305
|
-
if (added) this.extensions.update(peer)
|
|
306
406
|
const name = added ? 'peer-add' : 'peer-remove'
|
|
307
407
|
|
|
308
408
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
309
409
|
this.sessions[i].emit(name, peer)
|
|
410
|
+
|
|
411
|
+
if (added) {
|
|
412
|
+
for (const ext of this.sessions[i].extensions.values()) {
|
|
413
|
+
peer.extensions.set(ext.name, ext)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
310
416
|
}
|
|
311
417
|
}
|
|
312
418
|
|
|
@@ -323,19 +429,32 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
323
429
|
return null
|
|
324
430
|
}
|
|
325
431
|
|
|
326
|
-
async update () {
|
|
432
|
+
async update (opts) {
|
|
327
433
|
if (this.opened === false) await this.opening
|
|
434
|
+
|
|
328
435
|
// TODO: add an option where a writer can bootstrap it's state from the network also
|
|
329
|
-
if (this.writable) return false
|
|
330
|
-
|
|
436
|
+
if (this.writable || this.closing !== null) return false
|
|
437
|
+
|
|
438
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
439
|
+
const req = this.replicator.addUpgrade(activeRequests)
|
|
440
|
+
|
|
441
|
+
return req.promise
|
|
331
442
|
}
|
|
332
443
|
|
|
333
|
-
async seek (bytes) {
|
|
444
|
+
async seek (bytes, opts) {
|
|
334
445
|
if (this.opened === false) await this.opening
|
|
335
446
|
|
|
336
|
-
const s = this.core.tree.seek(bytes)
|
|
447
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
448
|
+
|
|
449
|
+
const offset = await s.update()
|
|
450
|
+
if (offset) return offset
|
|
337
451
|
|
|
338
|
-
|
|
452
|
+
if (this.closing !== null) throw new Error('Session is closed')
|
|
453
|
+
|
|
454
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
455
|
+
const req = this.replicator.addSeek(activeRequests, s)
|
|
456
|
+
|
|
457
|
+
return req.promise
|
|
339
458
|
}
|
|
340
459
|
|
|
341
460
|
async has (index) {
|
|
@@ -346,32 +465,67 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
346
465
|
|
|
347
466
|
async get (index, opts) {
|
|
348
467
|
if (this.opened === false) await this.opening
|
|
468
|
+
if (this.closing !== null) throw new Error('Session is closed')
|
|
469
|
+
|
|
470
|
+
const c = this.cache && this.cache.get(index)
|
|
471
|
+
if (c) return c
|
|
472
|
+
const fork = this.core.tree.fork
|
|
473
|
+
const b = await this._get(index, opts)
|
|
474
|
+
if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
|
|
475
|
+
return b
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async _get (index, opts) {
|
|
349
479
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
350
480
|
|
|
351
|
-
|
|
352
|
-
if (opts && opts.onwait) opts.onwait(index)
|
|
481
|
+
let block
|
|
353
482
|
|
|
354
|
-
|
|
355
|
-
|
|
483
|
+
if (this.core.bitfield.get(index)) {
|
|
484
|
+
block = await this.core.blocks.get(index)
|
|
485
|
+
} else {
|
|
486
|
+
if (opts && opts.wait === false) return null
|
|
487
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
356
488
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
360
|
-
const linear = !!(range && range.linear)
|
|
489
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
490
|
+
const req = this.replicator.addBlock(activeRequests, index)
|
|
361
491
|
|
|
362
|
-
|
|
492
|
+
block = await req.promise
|
|
493
|
+
}
|
|
363
494
|
|
|
364
|
-
|
|
495
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
496
|
+
return this._decode(encoding, block)
|
|
497
|
+
}
|
|
365
498
|
|
|
366
|
-
|
|
367
|
-
|
|
499
|
+
createReadStream (opts) {
|
|
500
|
+
return new ReadStream(this, opts)
|
|
501
|
+
}
|
|
368
502
|
|
|
369
|
-
|
|
503
|
+
createWriteStream (opts) {
|
|
504
|
+
return new WriteStream(this, opts)
|
|
370
505
|
}
|
|
371
506
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
507
|
+
download (range) {
|
|
508
|
+
const reqP = this._download(range)
|
|
509
|
+
|
|
510
|
+
// do not crash in the background...
|
|
511
|
+
reqP.catch(noop)
|
|
512
|
+
|
|
513
|
+
// TODO: turn this into an actual object...
|
|
514
|
+
return {
|
|
515
|
+
async downloaded () {
|
|
516
|
+
const req = await reqP
|
|
517
|
+
return req.promise
|
|
518
|
+
},
|
|
519
|
+
destroy () {
|
|
520
|
+
reqP.then(req => req.context && req.context.detach(req), noop)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async _download (range) {
|
|
526
|
+
if (this.opened === false) await this.opening
|
|
527
|
+
const activeRequests = (range && range.activeRequests) || this.activeRequests
|
|
528
|
+
return this.replicator.addRange(activeRequests, range)
|
|
375
529
|
}
|
|
376
530
|
|
|
377
531
|
// TODO: get rid of this / deprecate it?
|
|
@@ -379,6 +533,11 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
379
533
|
range.destroy(null)
|
|
380
534
|
}
|
|
381
535
|
|
|
536
|
+
// TODO: get rid of this / deprecate it?
|
|
537
|
+
cancel (request) {
|
|
538
|
+
// Do nothing for now
|
|
539
|
+
}
|
|
540
|
+
|
|
382
541
|
async truncate (newLength = 0, fork = -1) {
|
|
383
542
|
if (this.opened === false) await this.opening
|
|
384
543
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
@@ -394,40 +553,106 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
394
553
|
if (this.opened === false) await this.opening
|
|
395
554
|
if (this.writable === false) throw new Error('Core is not writable')
|
|
396
555
|
|
|
397
|
-
|
|
398
|
-
|
|
556
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
557
|
+
|
|
558
|
+
const preappend = this.encryption && this._preappend
|
|
399
559
|
|
|
400
|
-
|
|
401
|
-
const blk = blks[i]
|
|
560
|
+
const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
|
|
402
561
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
562
|
+
if (this.encodeBatch === null) {
|
|
563
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
564
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return await this.core.append(buffers, this.sign, { preappend })
|
|
569
|
+
}
|
|
408
570
|
|
|
409
|
-
|
|
571
|
+
async treeHash (length) {
|
|
572
|
+
if (length === undefined) {
|
|
573
|
+
await this.ready()
|
|
574
|
+
length = this.core.length
|
|
410
575
|
}
|
|
411
576
|
|
|
412
|
-
|
|
577
|
+
const roots = await this.core.tree.getRoots(length)
|
|
578
|
+
return this.crypto.tree(roots)
|
|
413
579
|
}
|
|
414
580
|
|
|
415
|
-
registerExtension (name, handlers) {
|
|
416
|
-
|
|
581
|
+
registerExtension (name, handlers = {}) {
|
|
582
|
+
if (this.extensions.has(name)) {
|
|
583
|
+
const ext = this.extensions.get(name)
|
|
584
|
+
ext.handlers = handlers
|
|
585
|
+
ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
|
|
586
|
+
ext.session = this
|
|
587
|
+
return ext
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const ext = {
|
|
591
|
+
name,
|
|
592
|
+
handlers,
|
|
593
|
+
encoding: c.from(codecs(handlers.encoding) || c.buffer),
|
|
594
|
+
session: this,
|
|
595
|
+
send (message, peer) {
|
|
596
|
+
const buffer = c.encode(this.encoding, message)
|
|
597
|
+
peer.extension(name, buffer)
|
|
598
|
+
},
|
|
599
|
+
broadcast (message) {
|
|
600
|
+
const buffer = c.encode(this.encoding, message)
|
|
601
|
+
for (const peer of this.session.peers) {
|
|
602
|
+
peer.extension(name, buffer)
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
destroy () {
|
|
606
|
+
for (const peer of this.session.peers) {
|
|
607
|
+
if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
|
|
608
|
+
}
|
|
609
|
+
this.session.extensions.delete(name)
|
|
610
|
+
},
|
|
611
|
+
_onmessage (state, peer) {
|
|
612
|
+
const m = this.encoding.decode(state)
|
|
613
|
+
if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
this.extensions.set(name, ext)
|
|
618
|
+
for (const peer of this.peers) {
|
|
619
|
+
peer.extensions.set(name, ext)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
return ext
|
|
417
623
|
}
|
|
418
624
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
625
|
+
_encode (enc, val) {
|
|
626
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
627
|
+
|
|
628
|
+
if (b4a.isBuffer(val)) {
|
|
629
|
+
if (state.start === 0) return val
|
|
630
|
+
state.end += val.byteLength
|
|
631
|
+
} else if (enc) {
|
|
632
|
+
enc.preencode(state, val)
|
|
633
|
+
} else {
|
|
634
|
+
val = b4a.from(val)
|
|
635
|
+
if (state.start === 0) return val
|
|
636
|
+
state.end += val.byteLength
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
640
|
+
|
|
641
|
+
if (enc) enc.encode(state, val)
|
|
642
|
+
else state.buffer.set(val, state.start)
|
|
643
|
+
|
|
644
|
+
return state.buffer
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
_decode (enc, block) {
|
|
648
|
+
block = block.subarray(this.padding)
|
|
649
|
+
if (enc) return c.decode(enc, block)
|
|
650
|
+
return block
|
|
422
651
|
}
|
|
423
652
|
}
|
|
424
653
|
|
|
425
654
|
function noop () {}
|
|
426
655
|
|
|
427
|
-
function decode (enc, buf) {
|
|
428
|
-
return enc ? c.decode(enc, buf) : buf
|
|
429
|
-
}
|
|
430
|
-
|
|
431
656
|
function isStream (s) {
|
|
432
657
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
433
658
|
}
|
|
@@ -441,5 +666,14 @@ function requireMaybe (name) {
|
|
|
441
666
|
}
|
|
442
667
|
|
|
443
668
|
function toHex (buf) {
|
|
444
|
-
return buf &&
|
|
669
|
+
return buf && b4a.toString(buf, 'hex')
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function preappend (blocks) {
|
|
673
|
+
const offset = this.core.tree.length
|
|
674
|
+
const fork = this.core.tree.fork
|
|
675
|
+
|
|
676
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
677
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
678
|
+
}
|
|
445
679
|
}
|