hypercore 10.0.0-alpha.4 → 10.0.0-alpha.40

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