hypercore 10.0.0-alpha.9 → 10.2.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/index.js CHANGED
@@ -1,18 +1,21 @@
1
1
  const { EventEmitter } = require('events')
2
- const raf = require('random-access-file')
2
+ 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')
6
7
  const Xache = require('xache')
7
8
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
8
- const codecs = require('codecs')
9
-
10
- const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
9
+ const Protomux = require('protomux')
10
+ const z32 = require('z32')
11
11
 
12
12
  const Replicator = require('./lib/replicator')
13
- const Extensions = require('./lib/extensions')
14
13
  const Core = require('./lib/core')
15
14
  const BlockEncryption = require('./lib/block-encryption')
15
+ const Info = require('./lib/info')
16
+ const Download = require('./lib/download')
17
+ const { ReadStream, WriteStream } = require('./lib/streams')
18
+ const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors')
16
19
 
17
20
  const promises = Symbol.for('hypercore.promises')
18
21
  const inspect = Symbol.for('nodejs.util.inspect.custom')
@@ -31,13 +34,13 @@ module.exports = class Hypercore extends EventEmitter {
31
34
  }
32
35
 
33
36
  if (key && typeof key === 'string') {
34
- key = Buffer.from(key, 'hex')
37
+ key = b4a.from(key, 'hex')
35
38
  }
36
39
 
37
40
  if (!opts) opts = {}
38
41
 
39
42
  if (!opts.crypto && key && key.byteLength !== 32) {
40
- throw new Error('Hypercore key should be 32 bytes')
43
+ throw BAD_ARGUMENT('Hypercore key should be 32 bytes')
41
44
  }
42
45
 
43
46
  if (!storage) storage = opts.storage
@@ -49,25 +52,35 @@ module.exports = class Hypercore extends EventEmitter {
49
52
  this.core = null
50
53
  this.replicator = null
51
54
  this.encryption = null
52
- this.extensions = opts.extensions || new Extensions()
55
+ this.extensions = new Map()
53
56
  this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
54
57
 
55
58
  this.valueEncoding = null
59
+ this.encodeBatch = null
60
+ this.activeRequests = []
61
+
62
+ this.id = null
56
63
  this.key = key || null
57
- this.discoveryKey = null
64
+ this.keyPair = null
58
65
  this.readable = true
59
66
  this.writable = false
60
67
  this.opened = false
61
68
  this.closed = false
69
+ this.snapshotted = !!opts.snapshot
70
+ this.sparse = opts.sparse !== false
62
71
  this.sessions = opts._sessions || [this]
63
- this.sign = opts.sign || null
72
+ this.auth = opts.auth || null
64
73
  this.autoClose = !!opts.autoClose
74
+ this.onwait = opts.onwait || null
75
+ this.wait = opts.wait !== false
65
76
 
66
77
  this.closing = null
67
- this.opening = opts._opening || this._open(key, storage, opts)
78
+ this.opening = this._openSession(key, storage, opts)
68
79
  this.opening.catch(noop)
69
80
 
70
81
  this._preappend = preappend.bind(this)
82
+ this._snapshot = null
83
+ this._findingPeers = 0
71
84
  }
72
85
 
73
86
  [inspect] (depth, opts) {
@@ -76,75 +89,286 @@ module.exports = class Hypercore extends EventEmitter {
76
89
  while (indent.length < opts.indentationLvl) indent += ' '
77
90
  }
78
91
 
92
+ let peers = ''
93
+ const min = Math.min(this.peers.length, 5)
94
+
95
+ for (let i = 0; i < min; i++) {
96
+ const peer = this.peers[i]
97
+
98
+ peers += indent + ' Peer(\n'
99
+ peers += indent + ' remotePublicKey: ' + opts.stylize(toHex(peer.remotePublicKey), 'string') + '\n'
100
+ peers += indent + ' remoteLength: ' + opts.stylize(peer.remoteLength, 'number') + '\n'
101
+ peers += indent + ' remoteFork: ' + opts.stylize(peer.remoteFork, 'number') + '\n'
102
+ peers += indent + ' remoteCanUpgrade: ' + opts.stylize(peer.remoteCanUpgrade, 'boolean') + '\n'
103
+ peers += indent + ' )' + '\n'
104
+ }
105
+
106
+ if (this.peers.length > 5) {
107
+ peers += indent + ' ... and ' + (this.peers.length - 5) + ' more\n'
108
+ }
109
+
110
+ if (peers) peers = '[\n' + peers + indent + ' ]'
111
+ else peers = '[ ' + opts.stylize(0, 'number') + ' ]'
112
+
79
113
  return this.constructor.name + '(\n' +
80
- indent + ' key: ' + opts.stylize((toHex(this.key)), 'string') + '\n' +
114
+ indent + ' key: ' + opts.stylize(toHex(this.key), 'string') + '\n' +
81
115
  indent + ' discoveryKey: ' + opts.stylize(toHex(this.discoveryKey), 'string') + '\n' +
82
116
  indent + ' opened: ' + opts.stylize(this.opened, 'boolean') + '\n' +
117
+ indent + ' closed: ' + opts.stylize(this.closed, 'boolean') + '\n' +
118
+ indent + ' snapshotted: ' + opts.stylize(this.snapshotted, 'boolean') + '\n' +
119
+ indent + ' sparse: ' + opts.stylize(this.sparse, 'boolean') + '\n' +
83
120
  indent + ' writable: ' + opts.stylize(this.writable, 'boolean') + '\n' +
84
- indent + ' sessions: ' + opts.stylize(this.sessions.length, 'number') + '\n' +
85
- indent + ' peers: [ ' + opts.stylize(this.peers.length, 'number') + ' ]\n' +
86
121
  indent + ' length: ' + opts.stylize(this.length, 'number') + '\n' +
87
- indent + ' byteLength: ' + opts.stylize(this.byteLength, 'number') + '\n' +
122
+ indent + ' fork: ' + opts.stylize(this.fork, 'number') + '\n' +
123
+ indent + ' sessions: [ ' + opts.stylize(this.sessions.length, 'number') + ' ]\n' +
124
+ indent + ' activeRequests: [ ' + opts.stylize(this.activeRequests.length, 'number') + ' ]\n' +
125
+ indent + ' peers: ' + peers + '\n' +
88
126
  indent + ')'
89
127
  }
90
128
 
91
- static createProtocolStream (isInitiator, opts) {
92
- const noiseStream = new NoiseSecretStream(isInitiator, null, opts)
93
- return noiseStream.rawStream
129
+ static getProtocolMuxer (stream) {
130
+ return stream.noiseStream.userData
131
+ }
132
+
133
+ static createProtocolStream (isInitiator, opts = {}) {
134
+ let outerStream = Protomux.isProtomux(isInitiator)
135
+ ? isInitiator.stream
136
+ : isStream(isInitiator)
137
+ ? isInitiator
138
+ : opts.stream
139
+
140
+ let noiseStream = null
141
+
142
+ if (outerStream) {
143
+ noiseStream = outerStream.noiseStream
144
+ } else {
145
+ noiseStream = new NoiseSecretStream(isInitiator, null, opts)
146
+ outerStream = noiseStream.rawStream
147
+ }
148
+ if (!noiseStream) throw BAD_ARGUMENT('Invalid stream')
149
+
150
+ if (!noiseStream.userData) {
151
+ const protocol = new Protomux(noiseStream)
152
+
153
+ if (opts.ondiscoverykey) {
154
+ protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
155
+ }
156
+ if (opts.keepAlive !== false) {
157
+ noiseStream.setKeepAlive(5000)
158
+ }
159
+ noiseStream.userData = protocol
160
+ }
161
+
162
+ return outerStream
94
163
  }
95
164
 
96
165
  static defaultStorage (storage, opts = {}) {
97
- if (typeof storage !== 'string') return storage
166
+ if (typeof storage !== 'string') {
167
+ if (!isRandomAccessClass(storage)) return storage
168
+ const Cls = storage // just to satisfy standard...
169
+ return name => new Cls(name)
170
+ }
171
+
98
172
  const directory = storage
99
173
  const toLock = opts.lock || 'oplog'
100
- return function createFile (name) {
101
- const locked = name === toLock || name.endsWith('/' + toLock)
102
- const lock = locked ? fsctl.lock : null
103
- const sparse = locked ? null : null // fsctl.sparse, disable sparse on windows - seems to fail for some people. TODO: investigate
104
- return raf(name, { directory, lock, sparse })
174
+
175
+ return createFile
176
+
177
+ function createFile (name) {
178
+ const lock = isFile(name, toLock)
179
+ const sparse = isFile(name, 'data') || isFile(name, 'bitfield') || isFile(name, 'tree')
180
+ return new RAF(name, { directory, lock, sparse })
181
+ }
182
+
183
+ function isFile (name, n) {
184
+ return name === n || name.endsWith('/' + n)
105
185
  }
106
186
  }
107
187
 
188
+ snapshot (opts) {
189
+ return this.session({ ...opts, snapshot: true })
190
+ }
191
+
108
192
  session (opts = {}) {
109
193
  if (this.closing) {
110
194
  // This makes the closing logic alot easier. If this turns out to be a problem
111
195
  // in practive, open an issue and we'll try to make a solution for it.
112
- throw new Error('Cannot make sessions on a closing core')
196
+ throw SESSION_CLOSED('Cannot make sessions on a closing core')
113
197
  }
114
198
 
199
+ const sparse = opts.sparse === false ? false : this.sparse
200
+ const wait = opts.wait === false ? false : this.wait
201
+ const onwait = opts.onwait === undefined ? this.onwait : opts.onwait
115
202
  const Clz = opts.class || Hypercore
116
- const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
117
-
118
- // This only works if the hypercore was fully loaded,
119
- // but we only do this to validate the keypair to help catch bugs so yolo
120
- if (this.key && keyPair) keyPair.publicKey = this.key
121
-
122
203
  const s = new Clz(this.storage, this.key, {
123
204
  ...opts,
124
- sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
125
- valueEncoding: this.valueEncoding,
126
- extensions: this.extensions,
205
+ sparse,
206
+ wait,
207
+ onwait,
127
208
  _opening: this.opening,
128
209
  _sessions: this.sessions
129
210
  })
130
211
 
131
- s._initSession(this)
212
+ s._passCapabilities(this)
213
+
214
+ // Configure the cache unless explicitly disabled.
215
+ if (opts.cache !== false) {
216
+ s.cache = opts.cache === true || !opts.cache ? this.cache : opts.cache
217
+ }
218
+
219
+ ensureEncryption(s, opts)
220
+
132
221
  this.sessions.push(s)
133
222
 
134
223
  return s
135
224
  }
136
225
 
137
- _initSession (o) {
138
- if (!this.sign) this.sign = o.sign
226
+ _passCapabilities (o) {
227
+ if (!this.auth) this.auth = o.auth
228
+
139
229
  this.crypto = o.crypto
140
- this.opened = o.opened
230
+ this.id = o.id
141
231
  this.key = o.key
142
- this.discoveryKey = o.discoveryKey
143
232
  this.core = o.core
144
233
  this.replicator = o.replicator
145
234
  this.encryption = o.encryption
146
- this.writable = !!this.sign
235
+ this.writable = !!(this.auth && this.auth.sign)
147
236
  this.autoClose = o.autoClose
237
+
238
+ if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
239
+ }
240
+
241
+ async _openFromExisting (from, opts) {
242
+ await from.opening
243
+
244
+ this._passCapabilities(from)
245
+ this.sessions = from.sessions
246
+ this.storage = from.storage
247
+ this.replicator.findingPeers += this._findingPeers
248
+
249
+ ensureEncryption(this, opts)
250
+
251
+ this.sessions.push(this)
252
+ }
253
+
254
+ async _openSession (key, storage, opts) {
255
+ const isFirst = !opts._opening
256
+
257
+ if (!isFirst) await opts._opening
258
+ if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
259
+
260
+ const keyPair = (key && opts.keyPair)
261
+ ? { ...opts.keyPair, publicKey: key }
262
+ : key
263
+ ? { publicKey: key, secretKey: null }
264
+ : opts.keyPair
265
+
266
+ // This only works if the hypercore was fully loaded,
267
+ // but we only do this to validate the keypair to help catch bugs so yolo
268
+ if (this.key && keyPair) keyPair.publicKey = this.key
269
+
270
+ if (opts.auth) {
271
+ this.auth = opts.auth
272
+ } else if (opts.sign) {
273
+ this.auth = Core.createAuth(this.crypto, keyPair, opts)
274
+ } else if (keyPair && keyPair.secretKey) {
275
+ this.auth = Core.createAuth(this.crypto, keyPair)
276
+ }
277
+
278
+ if (isFirst) {
279
+ await this._openCapabilities(keyPair, storage, opts)
280
+ // Only the root session should pass capabilities to other sessions.
281
+ for (let i = 0; i < this.sessions.length; i++) {
282
+ const s = this.sessions[i]
283
+ if (s !== this) s._passCapabilities(this)
284
+ }
285
+ }
286
+
287
+ if (!this.auth) this.auth = this.core.defaultAuth
288
+ this.writable = !!this.auth.sign
289
+
290
+ if (opts.valueEncoding) {
291
+ this.valueEncoding = c.from(opts.valueEncoding)
292
+ }
293
+ if (opts.encodeBatch) {
294
+ this.encodeBatch = opts.encodeBatch
295
+ }
296
+
297
+ // Start continous replication if not in sparse mode.
298
+ if (!this.sparse) this.download({ start: 0, end: -1 })
299
+
300
+ // This is a hidden option that's only used by Corestore.
301
+ // It's required so that corestore can load a name from userData before 'ready' is emitted.
302
+ if (opts._preready) await opts._preready(this)
303
+
304
+ this.opened = true
305
+ this.emit('ready')
306
+ }
307
+
308
+ async _openCapabilities (keyPair, storage, opts) {
309
+ if (opts.from) return this._openFromExisting(opts.from, opts)
310
+
311
+ this.storage = Hypercore.defaultStorage(opts.storage || storage)
312
+
313
+ this.core = await Core.open(this.storage, {
314
+ force: opts.force,
315
+ createIfMissing: opts.createIfMissing,
316
+ overwrite: opts.overwrite,
317
+ keyPair,
318
+ crypto: this.crypto,
319
+ legacy: opts.legacy,
320
+ auth: opts.auth,
321
+ onupdate: this._oncoreupdate.bind(this)
322
+ })
323
+
324
+ if (opts.userData) {
325
+ for (const [key, value] of Object.entries(opts.userData)) {
326
+ await this.core.userData(key, value)
327
+ }
328
+ }
329
+
330
+ this.key = this.core.header.signer.publicKey
331
+ this.keyPair = this.core.header.signer
332
+ this.id = z32.encode(this.key)
333
+
334
+ this.replicator = new Replicator(this.core, this.key, {
335
+ eagerUpdate: true,
336
+ allowFork: opts.allowFork !== false,
337
+ onpeerupdate: this._onpeerupdate.bind(this),
338
+ onupload: this._onupload.bind(this)
339
+ })
340
+
341
+ this.replicator.findingPeers += this._findingPeers
342
+
343
+ if (!this.encryption && opts.encryptionKey) {
344
+ this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
345
+ }
346
+ }
347
+
348
+ _getSnapshot () {
349
+ if (this.sparse) {
350
+ return {
351
+ length: this.core.tree.length,
352
+ byteLength: this.core.tree.byteLength,
353
+ fork: this.core.tree.fork,
354
+ compatLength: this.core.tree.length
355
+ }
356
+ }
357
+
358
+ return {
359
+ length: this.core.header.contiguousLength,
360
+ byteLength: 0,
361
+ fork: this.core.tree.fork,
362
+ compatLength: this.core.header.contiguousLength
363
+ }
364
+ }
365
+
366
+ _updateSnapshot () {
367
+ const prev = this._snapshot
368
+ const next = this._snapshot = this._getSnapshot()
369
+
370
+ if (!prev) return true
371
+ return prev.length !== next.length || prev.fork !== next.fork
148
372
  }
149
373
 
150
374
  close () {
@@ -163,6 +387,20 @@ module.exports = class Hypercore extends EventEmitter {
163
387
  this.readable = false
164
388
  this.writable = false
165
389
  this.closed = true
390
+ this.opened = false
391
+
392
+ const gc = []
393
+ for (const ext of this.extensions.values()) {
394
+ if (ext.session === this) gc.push(ext)
395
+ }
396
+ for (const ext of gc) ext.destroy()
397
+
398
+ if (this.replicator !== null) {
399
+ this.replicator.findingPeers -= this._findingPeers
400
+ this.replicator.clearRequests(this.activeRequests)
401
+ }
402
+
403
+ this._findingPeers = 0
166
404
 
167
405
  if (this.sessions.length) {
168
406
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
@@ -178,41 +416,63 @@ module.exports = class Hypercore extends EventEmitter {
178
416
  }
179
417
 
180
418
  replicate (isInitiator, opts = {}) {
181
- let outerStream = isStream(isInitiator)
182
- ? isInitiator
183
- : opts.stream
184
- let noiseStream = null
419
+ // Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
420
+ // because it doesn't really make a lot of sense.
421
+ if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator, opts)
185
422
 
186
- if (outerStream) {
187
- noiseStream = outerStream.noiseStream
188
- } else {
189
- outerStream = Hypercore.createProtocolStream(isInitiator, opts)
190
- noiseStream = outerStream.noiseStream
191
- }
192
- if (!noiseStream) throw new Error('Invalid stream passed to replicate')
423
+ const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
424
+ const noiseStream = protocolStream.noiseStream
425
+ const protocol = noiseStream.userData
193
426
 
194
- if (!noiseStream.userData) {
195
- const protocol = Replicator.createProtocol(noiseStream)
196
- noiseStream.userData = protocol
197
- noiseStream.on('error', noop) // All noise errors already propagate through outerStream
427
+ this._attachToMuxer(protocol, opts)
428
+
429
+ return protocolStream
430
+ }
431
+
432
+ _attachToMuxer (mux, opts) {
433
+ // If the user wants to, we can make this replication run in a session
434
+ // that way the core wont close "under them" during replication
435
+ if (opts.session) {
436
+ const s = this.session()
437
+ mux.stream.on('close', () => s.close().catch(noop))
198
438
  }
199
439
 
200
- const protocol = noiseStream.userData
201
440
  if (this.opened) {
202
- this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
441
+ this.replicator.attachTo(mux)
203
442
  } else {
204
- this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
443
+ this.opening.then(() => this.replicator.attachTo(mux), mux.destroy.bind(mux))
205
444
  }
206
445
 
207
- return outerStream
446
+ return mux
447
+ }
448
+
449
+ get discoveryKey () {
450
+ return this.replicator === null ? null : this.replicator.discoveryKey
208
451
  }
209
452
 
210
453
  get length () {
211
- return this.core === null ? 0 : this.core.tree.length
454
+ if (this._snapshot) return this._snapshot.length
455
+ if (this.core === null) return 0
456
+ if (!this.sparse) return this.contiguousLength
457
+ return this.core.tree.length
212
458
  }
213
459
 
460
+ /**
461
+ * Deprecated. Use `const { byteLength } = await core.info()`.
462
+ */
214
463
  get byteLength () {
215
- return this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding)
464
+ if (this._snapshot) return this._snapshot.byteLength
465
+ if (this.core === null) return 0
466
+ if (!this.sparse) return this.contiguousByteLength
467
+ return this.core.tree.byteLength - (this.core.tree.length * this.padding)
468
+ }
469
+
470
+ get contiguousLength () {
471
+ return this.core === null ? 0 : this.core.header.contiguousLength
472
+ }
473
+
474
+ get contiguousByteLength () {
475
+ return 0
216
476
  }
217
477
 
218
478
  get fork () {
@@ -235,111 +495,86 @@ module.exports = class Hypercore extends EventEmitter {
235
495
  return this.opening
236
496
  }
237
497
 
238
- async _open (key, storage, opts) {
239
- if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
240
-
241
- this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
242
-
243
- const keyPair = (key && opts.keyPair)
244
- ? { ...opts.keyPair, publicKey: key }
245
- : key
246
- ? { publicKey: key, secretKey: null }
247
- : opts.keyPair
498
+ _onupload (index, value, from) {
499
+ const byteLength = value.byteLength - this.padding
248
500
 
249
- if (opts.from) {
250
- const from = opts.from
251
- await from.opening
252
- for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
253
- this._initSession(from)
254
- this.extensions = from.extensions
255
- this.sessions = from.sessions
256
- this.storage = from.storage
257
- if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
258
- this.writable = !!this.sign
259
- this.sessions.push(this)
260
- return
501
+ for (let i = 0; i < this.sessions.length; i++) {
502
+ this.sessions[i].emit('upload', index, byteLength, from)
261
503
  }
504
+ }
262
505
 
263
- if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
506
+ _oncoreupdate (status, bitfield, value, from) {
507
+ if (status !== 0) {
508
+ const truncatedNonSparse = (status & 0b1000) !== 0
509
+ const appendedNonSparse = (status & 0b0100) !== 0
510
+ const truncated = (status & 0b0010) !== 0
511
+ const appended = (status & 0b0001) !== 0
264
512
 
265
- this.core = await Core.open(this.storage, {
266
- keyPair,
267
- crypto: this.crypto,
268
- onupdate: this._oncoreupdate.bind(this)
269
- })
513
+ if (truncated) {
514
+ this.replicator.ontruncate(bitfield.start)
515
+ }
270
516
 
271
- if (opts.userData) {
272
- for (const [key, value] of Object.entries(opts.userData)) {
273
- await this.core.userData(key, value)
517
+ if ((status & 0b0011) !== 0) {
518
+ this.replicator.onupgrade()
274
519
  }
275
- }
276
520
 
277
- this.replicator = new Replicator(this.core, {
278
- onupdate: this._onpeerupdate.bind(this)
279
- })
521
+ for (let i = 0; i < this.sessions.length; i++) {
522
+ const s = this.sessions[i]
280
523
 
281
- if (!this.sign) this.sign = opts.sign || this.core.defaultSign
524
+ if (truncated) {
525
+ if (s.cache) s.cache.clear()
282
526
 
283
- this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
284
- this.key = this.core.header.signer.publicKey
285
- this.writable = !!this.sign
527
+ // If snapshotted, make sure to update our compat so we can fail gets
528
+ if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
529
+ }
286
530
 
287
- if (!this.encryption && opts.encryptionKey) {
288
- this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
289
- }
531
+ if (s.sparse ? truncated : truncatedNonSparse) {
532
+ s.emit('truncate', bitfield.start, this.core.tree.fork)
533
+ }
290
534
 
291
- this.extensions.attach(this.replicator)
292
- this.opened = true
535
+ // For sparse sessions, immediately emit appends. If non-sparse, emit if contig length has updated
536
+ if (s.sparse ? appended : appendedNonSparse) {
537
+ s.emit('append')
538
+ }
539
+ }
293
540
 
294
- if (opts.postload) await opts.postload(this)
541
+ const contig = this.core.header.contiguousLength
295
542
 
296
- for (let i = 0; i < this.sessions.length; i++) {
297
- const s = this.sessions[i]
298
- if (s !== this) s._initSession(this)
299
- s.emit('ready')
300
- }
301
- }
543
+ // When the contig length catches up, broadcast the non-sparse length to peers
544
+ if (appendedNonSparse && contig === this.core.tree.length) {
545
+ for (const peer of this.peers) {
546
+ if (peer.broadcastedNonSparse) continue
302
547
 
303
- _oncoreupdate (status, bitfield, value, from) {
304
- if (status !== 0) {
305
- for (let i = 0; i < this.sessions.length; i++) {
306
- if ((status & 0b10) !== 0) {
307
- if (this.cache) this.cache.clear()
308
- this.sessions[i].emit('truncate', this.core.tree.fork)
309
- }
310
- if ((status & 0b01) !== 0) {
311
- this.sessions[i].emit('append')
548
+ peer.broadcastRange(0, contig)
549
+ peer.broadcastedNonSparse = true
312
550
  }
313
551
  }
314
-
315
- this.replicator.broadcastInfo()
316
552
  }
317
553
 
318
- if (bitfield && !bitfield.drop) { // TODO: support drop!
319
- for (let i = 0; i < bitfield.length; i++) {
320
- this.replicator.broadcastBlock(bitfield.start + i)
321
- }
554
+ if (bitfield) {
555
+ this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
322
556
  }
323
557
 
324
558
  if (value) {
325
- if (this.encryption) {
326
- this.encryption.decrypt(bitfield.start, value)
327
- }
328
-
329
- value = value.subarray(this.padding)
559
+ const byteLength = value.byteLength - this.padding
330
560
 
331
561
  for (let i = 0; i < this.sessions.length; i++) {
332
- this.sessions[i].emit('download', bitfield.start, value, from)
562
+ this.sessions[i].emit('download', bitfield.start, byteLength, from)
333
563
  }
334
564
  }
335
565
  }
336
566
 
337
567
  _onpeerupdate (added, peer) {
338
- if (added) this.extensions.update(peer)
339
568
  const name = added ? 'peer-add' : 'peer-remove'
340
569
 
341
570
  for (let i = 0; i < this.sessions.length; i++) {
342
571
  this.sessions[i].emit(name, peer)
572
+
573
+ if (added) {
574
+ for (const ext of this.sessions[i].extensions.values()) {
575
+ peer.extensions.set(ext.name, ext)
576
+ }
577
+ }
343
578
  }
344
579
  }
345
580
 
@@ -356,19 +591,72 @@ module.exports = class Hypercore extends EventEmitter {
356
591
  return null
357
592
  }
358
593
 
359
- async update () {
594
+ findingPeers () {
595
+ this._findingPeers++
596
+ if (this.replicator !== null && !this.closing) this.replicator.findingPeers++
597
+
598
+ let once = true
599
+
600
+ return () => {
601
+ if (this.closing || !once) return
602
+ once = false
603
+ this._findingPeers--
604
+ if (this.replicator !== null && --this.replicator.findingPeers === 0) {
605
+ this.replicator.updateAll()
606
+ }
607
+ }
608
+ }
609
+
610
+ async info () {
360
611
  if (this.opened === false) await this.opening
361
- // TODO: add an option where a writer can bootstrap it's state from the network also
362
- if (this.writable) return false
363
- return this.replicator.requestUpgrade()
612
+
613
+ return Info.from(this.core, this.padding, this._snapshot)
614
+ }
615
+
616
+ async update (opts) {
617
+ if (this.opened === false) await this.opening
618
+ if (this.closing !== null) return false
619
+
620
+ if (this.writable && (!opts || opts.force !== true)) {
621
+ if (!this.snapshotted) return false
622
+ return this._updateSnapshot()
623
+ }
624
+
625
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
626
+ const req = this.replicator.addUpgrade(activeRequests)
627
+
628
+ let upgraded = await req.promise
629
+
630
+ if (!this.sparse) {
631
+ // Download all available blocks in non-sparse mode
632
+ const start = this.length
633
+ const end = this.core.tree.length
634
+ const contig = this.contiguousLength
635
+
636
+ await this.download({ start, end, ifAvailable: true }).done()
637
+
638
+ if (!upgraded) upgraded = this.contiguousLength !== contig
639
+ }
640
+
641
+ if (!upgraded) return false
642
+ if (this.snapshotted) return this._updateSnapshot()
643
+ return true
364
644
  }
365
645
 
366
- async seek (bytes) {
646
+ async seek (bytes, opts) {
367
647
  if (this.opened === false) await this.opening
368
648
 
369
649
  const s = this.core.tree.seek(bytes, this.padding)
370
650
 
371
- return (await s.update()) || this.replicator.requestSeek(s)
651
+ const offset = await s.update()
652
+ if (offset) return offset
653
+
654
+ if (this.closing !== null) throw SESSION_CLOSED()
655
+
656
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
657
+ const req = this.replicator.addSeek(activeRequests, s)
658
+
659
+ return req.promise
372
660
  }
373
661
 
374
662
  async has (index) {
@@ -379,63 +667,97 @@ module.exports = class Hypercore extends EventEmitter {
379
667
 
380
668
  async get (index, opts) {
381
669
  if (this.opened === false) await this.opening
382
- const c = this.cache && this.cache.get(index)
383
- if (c) return c
384
- const fork = this.core.tree.fork
385
- const b = await this._get(index, opts)
386
- if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
387
- return b
670
+ if (this.closing !== null) throw SESSION_CLOSED()
671
+ if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
672
+
673
+ const encoding = (opts && opts.valueEncoding && c.from(opts.valueEncoding)) || this.valueEncoding
674
+
675
+ let req = this.cache && this.cache.get(index)
676
+ if (!req) req = this._get(index, opts)
677
+
678
+ let block = await req
679
+ if (!block) return null
680
+
681
+ if (this.encryption) {
682
+ // Copy the block as it might be shared with other sessions.
683
+ block = b4a.from(block)
684
+
685
+ this.encryption.decrypt(index, block)
686
+ }
687
+
688
+ return this._decode(encoding, block)
388
689
  }
389
690
 
390
- async _get (index, opts) {
391
- const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
691
+ async clear (start, end = start + 1, opts) {
692
+ if (this.opened === false) await this.opening
693
+ if (this.closing !== null) throw SESSION_CLOSED()
694
+
695
+ if (typeof end === 'object') {
696
+ opts = end
697
+ end = start + 1
698
+ }
699
+
700
+ if (start >= end) return
701
+
702
+ await this.core.clear(start, end)
703
+ }
392
704
 
705
+ async _get (index, opts) {
393
706
  let block
394
707
 
395
708
  if (this.core.bitfield.get(index)) {
396
- block = await this.core.blocks.get(index)
397
- if (this.encryption) this.encryption.decrypt(index, block)
709
+ block = this.core.blocks.get(index)
710
+
711
+ if (this.cache) this.cache.set(index, block)
398
712
  } else {
399
- if (opts && opts.onwait) opts.onwait(index)
400
- // Note that the _oncoreupdate handler decrypts inplace so we should not decrypt here
401
- block = await this.replicator.requestBlock(index)
713
+ if (opts && opts.wait === false) return null
714
+ if (this.wait === false && (!opts || !opts.wait)) return null
715
+ if (opts && opts.onwait) opts.onwait(index, this)
716
+ if (this.onwait) this.onwait(index, this)
717
+
718
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
719
+
720
+ const req = this.replicator.addBlock(activeRequests, index)
721
+
722
+ block = this._cacheOnResolve(index, req.promise, this.core.tree.fork)
402
723
  }
403
724
 
404
- return this._decode(encoding, block)
725
+ return block
405
726
  }
406
727
 
407
- download (range) {
408
- const linear = !!(range && range.linear)
728
+ async _cacheOnResolve (index, req, fork) {
729
+ const block = await req
409
730
 
410
- let start
411
- let end
412
- let filter
731
+ if (this.cache && fork === this.core.tree.fork) {
732
+ this.cache.set(index, Promise.resolve(block))
733
+ }
413
734
 
414
- if (range && range.blocks) {
415
- const blocks = range.blocks instanceof Set
416
- ? range.blocks
417
- : new Set(range.blocks)
735
+ return block
736
+ }
418
737
 
419
- start = range.start || (blocks.size ? min(range.blocks) : 0)
420
- end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
738
+ createReadStream (opts) {
739
+ return new ReadStream(this, opts)
740
+ }
421
741
 
422
- filter = (i) => blocks.has(i)
423
- } else {
424
- start = (range && range.start) || 0
425
- end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
426
- }
742
+ createWriteStream (opts) {
743
+ return new WriteStream(this, opts)
744
+ }
427
745
 
428
- const r = Replicator.createRange(start, end, filter, linear)
746
+ download (range) {
747
+ const req = this._download(range)
429
748
 
430
- if (this.opened) this.replicator.addRange(r)
431
- else this.opening.then(() => this.replicator.addRange(r), noop)
749
+ // do not crash in the background...
750
+ req.catch(noop)
432
751
 
433
- return r
752
+ return new Download(req)
434
753
  }
435
754
 
436
- // TODO: get rid of this / deprecate it?
437
- cancel (request) {
438
- // Do nothing for now
755
+ async _download (range) {
756
+ if (this.opened === false) await this.opening
757
+
758
+ const activeRequests = (range && range.activeRequests) || this.activeRequests
759
+
760
+ return this.replicator.addRange(activeRequests, range)
439
761
  }
440
762
 
441
763
  // TODO: get rid of this / deprecate it?
@@ -443,12 +765,17 @@ module.exports = class Hypercore extends EventEmitter {
443
765
  range.destroy(null)
444
766
  }
445
767
 
768
+ // TODO: get rid of this / deprecate it?
769
+ cancel (request) {
770
+ // Do nothing for now
771
+ }
772
+
446
773
  async truncate (newLength = 0, fork = -1) {
447
774
  if (this.opened === false) await this.opening
448
- if (this.writable === false) throw new Error('Core is not writable')
775
+ if (this.writable === false) throw SESSION_NOT_WRITABLE()
449
776
 
450
777
  if (fork === -1) fork = this.core.tree.fork + 1
451
- await this.core.truncate(newLength, fork, this.sign)
778
+ await this.core.truncate(newLength, fork, this.auth)
452
779
 
453
780
  // TODO: Should propagate from an event triggered by the oplog
454
781
  this.replicator.updateAll()
@@ -456,44 +783,92 @@ module.exports = class Hypercore extends EventEmitter {
456
783
 
457
784
  async append (blocks) {
458
785
  if (this.opened === false) await this.opening
459
- if (this.writable === false) throw new Error('Core is not writable')
786
+ if (this.writable === false) throw SESSION_NOT_WRITABLE()
460
787
 
461
788
  blocks = Array.isArray(blocks) ? blocks : [blocks]
462
789
 
463
790
  const preappend = this.encryption && this._preappend
464
- const buffers = new Array(blocks.length)
465
791
 
466
- for (let i = 0; i < blocks.length; i++) {
467
- buffers[i] = this._encode(this.valueEncoding, blocks[i])
792
+ const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
793
+
794
+ if (this.encodeBatch === null) {
795
+ for (let i = 0; i < blocks.length; i++) {
796
+ buffers[i] = this._encode(this.valueEncoding, blocks[i])
797
+ }
468
798
  }
469
799
 
470
- return await this.core.append(buffers, this.sign, { preappend })
800
+ return await this.core.append(buffers, this.auth, { preappend })
471
801
  }
472
802
 
473
- registerExtension (name, handlers) {
474
- return this.extensions.register(name, handlers)
803
+ async treeHash (length) {
804
+ if (length === undefined) {
805
+ await this.ready()
806
+ length = this.core.length
807
+ }
808
+
809
+ const roots = await this.core.tree.getRoots(length)
810
+ return this.crypto.tree(roots)
475
811
  }
476
812
 
477
- // called by the extensions
478
- onextensionupdate () {
479
- if (this.replicator !== null) this.replicator.broadcastOptions()
813
+ registerExtension (name, handlers = {}) {
814
+ if (this.extensions.has(name)) {
815
+ const ext = this.extensions.get(name)
816
+ ext.handlers = handlers
817
+ ext.encoding = c.from(handlers.encoding || c.buffer)
818
+ ext.session = this
819
+ return ext
820
+ }
821
+
822
+ const ext = {
823
+ name,
824
+ handlers,
825
+ encoding: c.from(handlers.encoding || c.buffer),
826
+ session: this,
827
+ send (message, peer) {
828
+ const buffer = c.encode(this.encoding, message)
829
+ peer.extension(name, buffer)
830
+ },
831
+ broadcast (message) {
832
+ const buffer = c.encode(this.encoding, message)
833
+ for (const peer of this.session.peers) {
834
+ peer.extension(name, buffer)
835
+ }
836
+ },
837
+ destroy () {
838
+ for (const peer of this.session.peers) {
839
+ if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
840
+ }
841
+ this.session.extensions.delete(name)
842
+ },
843
+ _onmessage (state, peer) {
844
+ const m = this.encoding.decode(state)
845
+ if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
846
+ }
847
+ }
848
+
849
+ this.extensions.set(name, ext)
850
+ for (const peer of this.peers) {
851
+ peer.extensions.set(name, ext)
852
+ }
853
+
854
+ return ext
480
855
  }
481
856
 
482
857
  _encode (enc, val) {
483
858
  const state = { start: this.padding, end: this.padding, buffer: null }
484
859
 
485
- if (Buffer.isBuffer(val)) {
860
+ if (b4a.isBuffer(val)) {
486
861
  if (state.start === 0) return val
487
862
  state.end += val.byteLength
488
863
  } else if (enc) {
489
864
  enc.preencode(state, val)
490
865
  } else {
491
- val = Buffer.from(val)
866
+ val = b4a.from(val)
492
867
  if (state.start === 0) return val
493
868
  state.end += val.byteLength
494
869
  }
495
870
 
496
- state.buffer = Buffer.allocUnsafe(state.end)
871
+ state.buffer = b4a.allocUnsafe(state.end)
497
872
 
498
873
  if (enc) enc.encode(state, val)
499
874
  else state.buffer.set(val, state.start)
@@ -502,7 +877,7 @@ module.exports = class Hypercore extends EventEmitter {
502
877
  }
503
878
 
504
879
  _decode (enc, block) {
505
- block = block.subarray(this.padding)
880
+ if (this.padding) block = block.subarray(this.padding)
506
881
  if (enc) return c.decode(enc, block)
507
882
  return block
508
883
  }
@@ -514,29 +889,12 @@ function isStream (s) {
514
889
  return typeof s === 'object' && s && typeof s.pipe === 'function'
515
890
  }
516
891
 
517
- function requireMaybe (name) {
518
- try {
519
- return require(name)
520
- } catch (_) {
521
- return null
522
- }
892
+ function isRandomAccessClass (fn) {
893
+ return !!(typeof fn === 'function' && fn.prototype && typeof fn.prototype.open === 'function')
523
894
  }
524
895
 
525
896
  function toHex (buf) {
526
- return buf && buf.toString('hex')
527
- }
528
-
529
- function reduce (iter, fn, acc) {
530
- for (const item of iter) acc = fn(acc, item)
531
- return acc
532
- }
533
-
534
- function min (arr) {
535
- return reduce(arr, (a, b) => Math.min(a, b), Infinity)
536
- }
537
-
538
- function max (arr) {
539
- return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
897
+ return buf && b4a.toString(buf, 'hex')
540
898
  }
541
899
 
542
900
  function preappend (blocks) {
@@ -547,3 +905,11 @@ function preappend (blocks) {
547
905
  this.encryption.encrypt(offset + i, blocks[i], fork)
548
906
  }
549
907
  }
908
+
909
+ function ensureEncryption (core, opts) {
910
+ if (!opts.encryptionKey) return
911
+ // Only override the block encryption if its either not already set or if
912
+ // the caller provided a different key.
913
+ if (core.encryption && b4a.equals(core.encryption.key, opts.encryptionKey)) return
914
+ core.encryption = new BlockEncryption(opts.encryptionKey, core.key)
915
+ }