hypercore 10.0.0-alpha.7 → 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
@@ -48,26 +49,35 @@ module.exports = class Hypercore extends EventEmitter {
48
49
  this.crypto = opts.crypto || hypercoreCrypto
49
50
  this.core = null
50
51
  this.replicator = null
51
- this.extensions = opts.extensions || new Extensions()
52
+ this.encryption = null
53
+ this.extensions = new Map()
52
54
  this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
53
- this.encryption = opts.encryption || 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
- this._preappend = this.encryption && preappend.bind(this)
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
- encryption: this.encryption,
127
- extensions: this.extensions,
202
+ sparse,
203
+ wait,
204
+ onwait,
128
205
  _opening: this.opening,
129
206
  _sessions: this.sessions
130
207
  })
131
208
 
132
- 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
+
133
218
  this.sessions.push(s)
134
219
 
135
220
  return s
136
221
  }
137
222
 
138
- _initSession (o) {
139
- if (!this.sign) this.sign = o.sign
223
+ _passCapabilities (o) {
224
+ if (!this.auth) this.auth = o.auth
225
+
140
226
  this.crypto = o.crypto
141
- this.opened = o.opened
142
227
  this.key = o.key
143
- this.discoveryKey = o.discoveryKey
144
228
  this.core = o.core
145
229
  this.replicator = o.replicator
146
- this.writable = !!this.sign
230
+ this.encryption = o.encryption
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,112 +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
- this._preappend = preappend.bind(this)
290
- }
526
+ if (s.sparse ? truncated : truncatedNonSparse) {
527
+ s.emit('truncate', bitfield.start, this.core.tree.fork)
528
+ }
291
529
 
292
- this.extensions.attach(this.replicator)
293
- 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
+ }
294
535
 
295
- if (opts.postload) await opts.postload(this)
536
+ const contig = this.core.header.contiguousLength
296
537
 
297
- for (let i = 0; i < this.sessions.length; i++) {
298
- const s = this.sessions[i]
299
- if (s !== this) s._initSession(this)
300
- s.emit('ready')
301
- }
302
- }
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
303
542
 
304
- _oncoreupdate (status, bitfield, value, from) {
305
- if (status !== 0) {
306
- for (let i = 0; i < this.sessions.length; i++) {
307
- if ((status & 0b10) !== 0) {
308
- if (this.cache) this.cache.clear()
309
- this.sessions[i].emit('truncate', this.core.tree.fork)
310
- }
311
- if ((status & 0b01) !== 0) {
312
- this.sessions[i].emit('append')
543
+ peer.broadcastRange(0, contig)
544
+ peer.broadcastedNonSparse = true
313
545
  }
314
546
  }
315
-
316
- this.replicator.broadcastInfo()
317
547
  }
318
548
 
319
- if (bitfield && !bitfield.drop) { // TODO: support drop!
320
- for (let i = 0; i < bitfield.length; i++) {
321
- this.replicator.broadcastBlock(bitfield.start + i)
322
- }
549
+ if (bitfield) {
550
+ this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
323
551
  }
324
552
 
325
553
  if (value) {
326
- if (this.encryption) {
327
- this.encryption.decrypt(bitfield.start, value)
328
- }
329
-
330
- value = value.subarray(this.padding)
554
+ const byteLength = value.byteLength - this.padding
331
555
 
332
556
  for (let i = 0; i < this.sessions.length; i++) {
333
- this.sessions[i].emit('download', bitfield.start, value, from)
557
+ this.sessions[i].emit('download', bitfield.start, byteLength, from)
334
558
  }
335
559
  }
336
560
  }
337
561
 
338
562
  _onpeerupdate (added, peer) {
339
- if (added) this.extensions.update(peer)
340
563
  const name = added ? 'peer-add' : 'peer-remove'
341
564
 
342
565
  for (let i = 0; i < this.sessions.length; i++) {
343
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
+ }
344
573
  }
345
574
  }
346
575
 
@@ -357,19 +586,73 @@ module.exports = class Hypercore extends EventEmitter {
357
586
  return null
358
587
  }
359
588
 
360
- 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 () {
361
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
+
362
615
  // TODO: add an option where a writer can bootstrap it's state from the network also
363
- if (this.writable) return false
364
- 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
365
640
  }
366
641
 
367
- async seek (bytes) {
642
+ async seek (bytes, opts) {
368
643
  if (this.opened === false) await this.opening
369
644
 
370
645
  const s = this.core.tree.seek(bytes, this.padding)
371
646
 
372
- 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
373
656
  }
374
657
 
375
658
  async has (index) {
@@ -380,75 +663,124 @@ module.exports = class Hypercore extends EventEmitter {
380
663
 
381
664
  async get (index, opts) {
382
665
  if (this.opened === false) await this.opening
383
- const c = this.cache && this.cache.get(index)
384
- if (c) return c
385
- const fork = this.core.tree.fork
386
- const b = await this._get(index, opts)
387
- if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
388
- 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)
389
685
  }
390
686
 
391
- async _get (index, opts) {
392
- 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
+ }
393
700
 
701
+ async _get (index, opts) {
394
702
  let block
395
703
 
396
704
  if (this.core.bitfield.get(index)) {
397
- block = await this.core.blocks.get(index)
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
- 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)
401
719
  }
402
720
 
403
- if (this.encryption) this.encryption.decrypt(index, block)
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
+ }
413
730
 
414
- if (range && range.blocks) {
415
- const blocks = range.blocks instanceof Set
416
- ? range.blocks
417
- : new Set(range.blocks)
731
+ return block
732
+ }
418
733
 
419
- start = range.start || (blocks.size ? min(range.blocks) : 0)
420
- end = range.end || (blocks.size ? max(range.blocks) + 1 : 0)
734
+ createReadStream (opts) {
735
+ return new ReadStream(this, opts)
736
+ }
421
737
 
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
738
+ createWriteStream (opts) {
739
+ return new WriteStream(this, opts)
740
+ }
741
+
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,45 +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
- const buffers = new Array(blocks.length)
795
+ const preappend = this.encryption && this._preappend
796
+
797
+ const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
464
798
 
465
- for (let i = 0; i < blocks.length; i++) {
466
- buffers[i] = this._encode(this.valueEncoding, blocks[i])
799
+ if (this.encodeBatch === null) {
800
+ for (let i = 0; i < blocks.length; i++) {
801
+ buffers[i] = this._encode(this.valueEncoding, blocks[i])
802
+ }
467
803
  }
468
804
 
469
- return await this.core.append(buffers, this.sign, {
470
- preappend: this._preappend
471
- })
805
+ return await this.core.append(buffers, this.auth, { preappend })
472
806
  }
473
807
 
474
- registerExtension (name, handlers) {
475
- 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)
476
816
  }
477
817
 
478
- // called by the extensions
479
- onextensionupdate () {
480
- 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
481
860
  }
482
861
 
483
862
  _encode (enc, val) {
484
863
  const state = { start: this.padding, end: this.padding, buffer: null }
485
864
 
486
- if (Buffer.isBuffer(val)) {
865
+ if (b4a.isBuffer(val)) {
487
866
  if (state.start === 0) return val
488
867
  state.end += val.byteLength
489
868
  } else if (enc) {
490
869
  enc.preencode(state, val)
491
870
  } else {
492
- val = Buffer.from(val)
871
+ val = b4a.from(val)
493
872
  if (state.start === 0) return val
494
873
  state.end += val.byteLength
495
874
  }
496
875
 
497
- state.buffer = Buffer.allocUnsafe(state.end)
876
+ state.buffer = b4a.allocUnsafe(state.end)
498
877
 
499
878
  if (enc) enc.encode(state, val)
500
879
  else state.buffer.set(val, state.start)
@@ -503,7 +882,7 @@ module.exports = class Hypercore extends EventEmitter {
503
882
  }
504
883
 
505
884
  _decode (enc, block) {
506
- block = block.subarray(this.padding)
885
+ if (this.padding) block = block.subarray(this.padding)
507
886
  if (enc) return c.decode(enc, block)
508
887
  return block
509
888
  }
@@ -515,29 +894,12 @@ function isStream (s) {
515
894
  return typeof s === 'object' && s && typeof s.pipe === 'function'
516
895
  }
517
896
 
518
- function requireMaybe (name) {
519
- try {
520
- return require(name)
521
- } catch (_) {
522
- return null
523
- }
897
+ function isRandomAccessClass (fn) {
898
+ return !!(typeof fn === 'function' && fn.prototype && typeof fn.prototype.open === 'function')
524
899
  }
525
900
 
526
901
  function toHex (buf) {
527
- return buf && buf.toString('hex')
528
- }
529
-
530
- function reduce (iter, fn, acc) {
531
- for (const item of iter) acc = fn(acc, item)
532
- return acc
533
- }
534
-
535
- function min (arr) {
536
- return reduce(arr, (a, b) => Math.min(a, b), Infinity)
537
- }
538
-
539
- function max (arr) {
540
- return reduce(arr, (a, b) => Math.max(a, b), -Infinity)
902
+ return buf && b4a.toString(buf, 'hex')
541
903
  }
542
904
 
543
905
  function preappend (blocks) {
@@ -548,3 +910,11 @@ function preappend (blocks) {
548
910
  this.encryption.encrypt(offset + i, blocks[i], fork)
549
911
  }
550
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
+ }