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