hypercore 10.0.0-alpha.9 → 10.0.0

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