hypercore 10.0.0-alpha.5 → 10.0.0-alpha.50

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