hypercore 10.0.0-alpha.4 → 10.0.0-alpha.42

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