hypercore 10.0.0-alpha.3 → 10.0.0-alpha.30

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,14 +3,18 @@ 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')
7
+ const Xache = require('xache')
6
8
  const NoiseSecretStream = require('@hyperswarm/secret-stream')
9
+ const Protomux = require('protomux')
7
10
  const codecs = require('codecs')
8
11
 
9
12
  const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
10
13
 
11
14
  const Replicator = require('./lib/replicator')
12
- const Extensions = require('./lib/extensions')
13
15
  const Core = require('./lib/core')
16
+ const BlockEncryption = require('./lib/block-encryption')
17
+ const { ReadStream, WriteStream } = require('./lib/streams')
14
18
 
15
19
  const promises = Symbol.for('hypercore.promises')
16
20
  const inspect = Symbol.for('nodejs.util.inspect.custom')
@@ -27,14 +31,17 @@ module.exports = class Hypercore extends EventEmitter {
27
31
  opts = key
28
32
  key = null
29
33
  }
34
+
30
35
  if (key && typeof key === 'string') {
31
- key = Buffer.from(key, 'hex')
36
+ key = b4a.from(key, 'hex')
32
37
  }
33
- if (key && key.byteLength !== 32) {
38
+
39
+ if (!opts) opts = {}
40
+
41
+ if (!opts.crypto && key && key.byteLength !== 32) {
34
42
  throw new Error('Hypercore key should be 32 bytes')
35
43
  }
36
44
 
37
- if (!opts) opts = {}
38
45
  if (!storage) storage = opts.storage
39
46
 
40
47
  this[promises] = true
@@ -43,11 +50,16 @@ module.exports = class Hypercore extends EventEmitter {
43
50
  this.crypto = opts.crypto || hypercoreCrypto
44
51
  this.core = null
45
52
  this.replicator = null
46
- this.extensions = opts.extensions || new Extensions()
53
+ this.encryption = null
54
+ this.extensions = new Map()
55
+ this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
47
56
 
48
57
  this.valueEncoding = null
58
+ this.encodeBatch = null
59
+ this.activeRequests = []
60
+
49
61
  this.key = key || null
50
- this.discoveryKey = null
62
+ this.keyPair = null
51
63
  this.readable = true
52
64
  this.writable = false
53
65
  this.opened = false
@@ -57,8 +69,11 @@ module.exports = class Hypercore extends EventEmitter {
57
69
  this.autoClose = !!opts.autoClose
58
70
 
59
71
  this.closing = null
60
- this.opening = opts._opening || this._open(key, storage, opts)
72
+ this.opening = this._openSession(key, storage, opts)
61
73
  this.opening.catch(noop)
74
+
75
+ this._preappend = preappend.bind(this)
76
+ this._snapshot = opts.snapshot || null
62
77
  }
63
78
 
64
79
  [inspect] (depth, opts) {
@@ -79,9 +94,41 @@ module.exports = class Hypercore extends EventEmitter {
79
94
  indent + ')'
80
95
  }
81
96
 
82
- static createProtocolStream (isInitiator, opts) {
83
- const noiseStream = new NoiseSecretStream(isInitiator, null, opts)
84
- return noiseStream.rawStream
97
+ static getProtocolMuxer (stream) {
98
+ return stream.noiseStream.userData
99
+ }
100
+
101
+ static createProtocolStream (isInitiator, opts = {}) {
102
+ let outerStream = Protomux.isProtomux(isInitiator)
103
+ ? isInitiator.stream
104
+ : isStream(isInitiator)
105
+ ? isInitiator
106
+ : opts.stream
107
+
108
+ let noiseStream = null
109
+
110
+ if (outerStream) {
111
+ noiseStream = outerStream.noiseStream
112
+ } else {
113
+ noiseStream = new NoiseSecretStream(isInitiator, null, opts)
114
+ outerStream = noiseStream.rawStream
115
+ }
116
+ if (!noiseStream) throw new Error('Invalid stream')
117
+
118
+ if (!noiseStream.userData) {
119
+ const protocol = new Protomux(noiseStream)
120
+
121
+ if (opts.ondiscoverykey) {
122
+ protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
123
+ }
124
+ if (opts.keepAlive !== false) {
125
+ noiseStream.setKeepAlive(5000)
126
+ noiseStream.setTimeout(7000)
127
+ }
128
+ noiseStream.userData = protocol
129
+ }
130
+
131
+ return outerStream
85
132
  }
86
133
 
87
134
  static defaultStorage (storage, opts = {}) {
@@ -96,6 +143,10 @@ module.exports = class Hypercore extends EventEmitter {
96
143
  }
97
144
  }
98
145
 
146
+ snapshot () {
147
+ return this.session({ snapshot: { length: this.length, byteLength: this.byteLength, fork: this.fork } })
148
+ }
149
+
99
150
  session (opts = {}) {
100
151
  if (this.closing) {
101
152
  // This makes the closing logic alot easier. If this turns out to be a problem
@@ -104,39 +155,124 @@ module.exports = class Hypercore extends EventEmitter {
104
155
  }
105
156
 
106
157
  const Clz = opts.class || Hypercore
107
- const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
108
-
109
- // This only works if the hypercore was fully loaded,
110
- // but we only do this to validate the keypair to help catch bugs so yolo
111
- if (this.key && keyPair) keyPair.publicKey = this.key
112
-
113
158
  const s = new Clz(this.storage, this.key, {
114
159
  ...opts,
115
- sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
116
- valueEncoding: this.valueEncoding,
117
- extensions: this.extensions,
118
160
  _opening: this.opening,
119
161
  _sessions: this.sessions
120
162
  })
121
163
 
122
- s._initSession(this)
164
+ s._passCapabilities(this)
123
165
  this.sessions.push(s)
124
166
 
125
167
  return s
126
168
  }
127
169
 
128
- _initSession (o) {
170
+ _passCapabilities (o) {
129
171
  if (!this.sign) this.sign = o.sign
130
172
  this.crypto = o.crypto
131
- this.opened = o.opened
132
173
  this.key = o.key
133
- this.discoveryKey = o.discoveryKey
134
174
  this.core = o.core
135
175
  this.replicator = o.replicator
176
+ this.encryption = o.encryption
136
177
  this.writable = !!this.sign
137
178
  this.autoClose = o.autoClose
138
179
  }
139
180
 
181
+ async _openFromExisting (from, opts) {
182
+ await from.opening
183
+
184
+ this._passCapabilities(from)
185
+ this.sessions = from.sessions
186
+ this.storage = from.storage
187
+
188
+ this.sessions.push(this)
189
+ }
190
+
191
+ async _openSession (key, storage, opts) {
192
+ const isFirst = !opts._opening
193
+
194
+ if (!isFirst) await opts._opening
195
+ if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
196
+
197
+ const keyPair = (key && opts.keyPair)
198
+ ? { ...opts.keyPair, publicKey: key }
199
+ : key
200
+ ? { publicKey: key, secretKey: null }
201
+ : opts.keyPair
202
+
203
+ // This only works if the hypercore was fully loaded,
204
+ // but we only do this to validate the keypair to help catch bugs so yolo
205
+ if (this.key && keyPair) keyPair.publicKey = this.key
206
+
207
+ if (opts.sign) {
208
+ this.sign = opts.sign
209
+ } else if (keyPair && keyPair.secretKey) {
210
+ this.sign = Core.createSigner(this.crypto, keyPair)
211
+ }
212
+
213
+ if (isFirst) {
214
+ await this._openCapabilities(keyPair, storage, opts)
215
+ // Only the root session should pass capabilities to other sessions.
216
+ for (let i = 0; i < this.sessions.length; i++) {
217
+ const s = this.sessions[i]
218
+ if (s !== this) s._passCapabilities(this)
219
+ }
220
+ }
221
+
222
+ if (!this.sign) this.sign = this.core.defaultSign
223
+ this.writable = !!this.sign
224
+
225
+ if (opts.valueEncoding) {
226
+ this.valueEncoding = c.from(codecs(opts.valueEncoding))
227
+ }
228
+ if (opts.encodeBatch) {
229
+ this.encodeBatch = opts.encodeBatch
230
+ }
231
+
232
+ // This is a hidden option that's only used by Corestore.
233
+ // It's required so that corestore can load a name from userData before 'ready' is emitted.
234
+ if (opts._preready) await opts._preready(this)
235
+
236
+ this.opened = true
237
+ this.emit('ready')
238
+ }
239
+
240
+ async _openCapabilities (keyPair, storage, opts) {
241
+ if (opts.from) return this._openFromExisting(opts.from, opts)
242
+
243
+ this.storage = Hypercore.defaultStorage(opts.storage || storage)
244
+
245
+ this.core = await Core.open(this.storage, {
246
+ force: opts.force,
247
+ createIfMissing: opts.createIfMissing,
248
+ overwrite: opts.overwrite,
249
+ keyPair,
250
+ crypto: this.crypto,
251
+ legacy: opts.legacy,
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.key = this.core.header.signer.publicKey
262
+ this.keyPair = this.core.header.signer
263
+
264
+ this.replicator = new Replicator(this.core, this.key, {
265
+ eagerUpdate: true,
266
+ allowFork: opts.allowFork !== false,
267
+ onpeerupdate: this._onpeerupdate.bind(this),
268
+ onupload: this._onupload.bind(this)
269
+ })
270
+
271
+ if (!this.encryption && opts.encryptionKey) {
272
+ this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
273
+ }
274
+ }
275
+
140
276
  close () {
141
277
  if (this.closing) return this.closing
142
278
  this.closing = this._close()
@@ -153,6 +289,17 @@ module.exports = class Hypercore extends EventEmitter {
153
289
  this.readable = false
154
290
  this.writable = false
155
291
  this.closed = true
292
+ this.opened = false
293
+
294
+ const gc = []
295
+ for (const ext of this.extensions.values()) {
296
+ if (ext.session === this) gc.push(ext)
297
+ }
298
+ for (const ext of gc) ext.destroy()
299
+
300
+ if (this.replicator !== null) {
301
+ this.replicator.clearRequests(this.activeRequests)
302
+ }
156
303
 
157
304
  if (this.sessions.length) {
158
305
  // if this is the last session and we are auto closing, trigger that first to enforce error handling
@@ -168,145 +315,104 @@ module.exports = class Hypercore extends EventEmitter {
168
315
  }
169
316
 
170
317
  replicate (isInitiator, opts = {}) {
171
- let outerStream = isStream(isInitiator)
172
- ? isInitiator
173
- : opts.stream
174
- let noiseStream = null
175
-
176
- if (outerStream) {
177
- noiseStream = outerStream.noiseStream
178
- } else {
179
- outerStream = Hypercore.createProtocolStream(isInitiator, opts)
180
- noiseStream = outerStream.noiseStream
181
- }
182
- if (!noiseStream) throw new Error('Invalid stream passed to replicate')
183
-
184
- if (!noiseStream.userData) {
185
- const protocol = Replicator.createProtocol(noiseStream)
186
- noiseStream.userData = protocol
187
- noiseStream.on('error', noop) // All noise errors already propagate through outerStream
188
- }
189
-
318
+ const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
319
+ const noiseStream = protocolStream.noiseStream
190
320
  const protocol = noiseStream.userData
321
+
191
322
  if (this.opened) {
192
- this.replicator.joinProtocol(protocol, this.key, this.discoveryKey)
323
+ this.replicator.attachTo(protocol)
193
324
  } else {
194
- this.opening.then(() => this.replicator.joinProtocol(protocol, this.key, this.discoveryKey), protocol.destroy.bind(protocol))
325
+ this.opening.then(() => this.replicator.attachTo(protocol), protocol.destroy.bind(protocol))
195
326
  }
196
327
 
197
- return outerStream
328
+ return protocolStream
329
+ }
330
+
331
+ get discoveryKey () {
332
+ return this.replicator === null ? null : this.replicator.discoveryKey
198
333
  }
199
334
 
200
335
  get length () {
201
- return this.core === null ? 0 : this.core.tree.length
336
+ return this._snapshot
337
+ ? this._snapshot.length
338
+ : (this.core === null ? 0 : this.core.tree.length)
202
339
  }
203
340
 
204
341
  get byteLength () {
205
- return this.core === null ? 0 : this.core.tree.byteLength
342
+ return this._snapshot
343
+ ? this._snapshot.byteLength
344
+ : (this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding))
206
345
  }
207
346
 
208
347
  get fork () {
209
- return this.core === null ? 0 : this.core.tree.fork
348
+ return this._snapshot
349
+ ? this._snapshot.fork
350
+ : (this.core === null ? 0 : this.core.tree.fork)
210
351
  }
211
352
 
212
353
  get peers () {
213
354
  return this.replicator === null ? [] : this.replicator.peers
214
355
  }
215
356
 
216
- ready () {
217
- return this.opening
357
+ get encryptionKey () {
358
+ return this.encryption && this.encryption.key
218
359
  }
219
360
 
220
- async _open (key, storage, opts) {
221
- if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
222
-
223
- this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
224
-
225
- const keyPair = (key && opts.keyPair)
226
- ? { ...opts.keyPair, publicKey: key }
227
- : key
228
- ? { publicKey: key, secretKey: null }
229
- : opts.keyPair
230
-
231
- if (opts.from) {
232
- const from = opts.from
233
- await from.opening
234
- for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
235
- this._initSession(from)
236
- this.extensions = from.extensions
237
- this.sessions = from.sessions
238
- this.storage = from.storage
239
- if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
240
- this.writable = !!this.sign
241
- this.sessions.push(this)
242
- return
243
- }
244
-
245
- if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
246
-
247
- this.core = await Core.open(this.storage, {
248
- keyPair,
249
- crypto: this.crypto,
250
- onupdate: this._oncoreupdate.bind(this)
251
- })
252
-
253
- if (opts.userData) {
254
- for (const [key, value] of Object.entries(opts.userData)) {
255
- await this.core.userData(key, value)
256
- }
257
- }
258
-
259
- this.replicator = new Replicator(this.core, {
260
- onupdate: this._onpeerupdate.bind(this)
261
- })
262
-
263
- if (!this.sign) this.sign = opts.sign || this.core.defaultSign
264
-
265
- this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
266
- this.key = this.core.header.signer.publicKey
267
- this.writable = !!this.sign
361
+ get padding () {
362
+ return this.encryption === null ? 0 : this.encryption.padding
363
+ }
268
364
 
269
- this.extensions.attach(this.replicator)
270
- this.opened = true
365
+ ready () {
366
+ return this.opening
367
+ }
271
368
 
272
- if (opts.postload) await opts.postload(this)
369
+ _onupload (index, value, from) {
370
+ const byteLength = value.byteLength - this.padding
273
371
 
274
372
  for (let i = 0; i < this.sessions.length; i++) {
275
- const s = this.sessions[i]
276
- if (s !== this) s._initSession(this)
277
- s.emit('ready')
373
+ this.sessions[i].emit('upload', index, byteLength, from)
278
374
  }
279
375
  }
280
376
 
281
377
  _oncoreupdate (status, bitfield, value, from) {
282
378
  if (status !== 0) {
283
379
  for (let i = 0; i < this.sessions.length; i++) {
284
- if ((status & 0b10) !== 0) this.sessions[i].emit('truncate', this.core.tree.fork)
285
- if ((status & 0b01) !== 0) this.sessions[i].emit('append')
380
+ if ((status & 0b10) !== 0) {
381
+ if (this.cache) this.cache.clear()
382
+ this.sessions[i].emit('truncate', bitfield.start, this.core.tree.fork)
383
+ }
384
+ if ((status & 0b01) !== 0) {
385
+ this.sessions[i].emit('append')
386
+ }
286
387
  }
287
388
 
288
- this.replicator.broadcastInfo()
389
+ this.replicator.localUpgrade()
289
390
  }
290
391
 
291
- if (bitfield && !bitfield.drop) { // TODO: support drop!
292
- for (let i = 0; i < bitfield.length; i++) {
293
- this.replicator.broadcastBlock(bitfield.start + i)
294
- }
392
+ if (bitfield) {
393
+ this.replicator.broadcastRange(bitfield.start, bitfield.length, bitfield.drop)
295
394
  }
296
395
 
297
396
  if (value) {
397
+ const byteLength = value.byteLength - this.padding
398
+
298
399
  for (let i = 0; i < this.sessions.length; i++) {
299
- this.sessions[i].emit('download', bitfield.start, value, from)
400
+ this.sessions[i].emit('download', bitfield.start, byteLength, from)
300
401
  }
301
402
  }
302
403
  }
303
404
 
304
405
  _onpeerupdate (added, peer) {
305
- if (added) this.extensions.update(peer)
306
406
  const name = added ? 'peer-add' : 'peer-remove'
307
407
 
308
408
  for (let i = 0; i < this.sessions.length; i++) {
309
409
  this.sessions[i].emit(name, peer)
410
+
411
+ if (added) {
412
+ for (const ext of this.sessions[i].extensions.values()) {
413
+ peer.extensions.set(ext.name, ext)
414
+ }
415
+ }
310
416
  }
311
417
  }
312
418
 
@@ -323,19 +429,32 @@ module.exports = class Hypercore extends EventEmitter {
323
429
  return null
324
430
  }
325
431
 
326
- async update () {
432
+ async update (opts) {
327
433
  if (this.opened === false) await this.opening
434
+
328
435
  // TODO: add an option where a writer can bootstrap it's state from the network also
329
- if (this.writable) return false
330
- return this.replicator.requestUpgrade()
436
+ if (this.writable || this.closing !== null) return false
437
+
438
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
439
+ const req = this.replicator.addUpgrade(activeRequests)
440
+
441
+ return req.promise
331
442
  }
332
443
 
333
- async seek (bytes) {
444
+ async seek (bytes, opts) {
334
445
  if (this.opened === false) await this.opening
335
446
 
336
- const s = this.core.tree.seek(bytes)
447
+ const s = this.core.tree.seek(bytes, this.padding)
448
+
449
+ const offset = await s.update()
450
+ if (offset) return offset
337
451
 
338
- return (await s.update()) || this.replicator.requestSeek(s)
452
+ if (this.closing !== null) throw new Error('Session is closed')
453
+
454
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
455
+ const req = this.replicator.addSeek(activeRequests, s)
456
+
457
+ return req.promise
339
458
  }
340
459
 
341
460
  async has (index) {
@@ -346,32 +465,67 @@ module.exports = class Hypercore extends EventEmitter {
346
465
 
347
466
  async get (index, opts) {
348
467
  if (this.opened === false) await this.opening
468
+ if (this.closing !== null) throw new Error('Session is closed')
469
+
470
+ const c = this.cache && this.cache.get(index)
471
+ if (c) return c
472
+ const fork = this.core.tree.fork
473
+ const b = await this._get(index, opts)
474
+ if (this.cache && fork === this.core.tree.fork && b) this.cache.set(index, b)
475
+ return b
476
+ }
477
+
478
+ async _get (index, opts) {
349
479
  const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
350
480
 
351
- if (this.core.bitfield.get(index)) return decode(encoding, await this.core.blocks.get(index))
352
- if (opts && opts.onwait) opts.onwait(index)
481
+ let block
353
482
 
354
- return decode(encoding, await this.replicator.requestBlock(index))
355
- }
483
+ if (this.core.bitfield.get(index)) {
484
+ block = await this.core.blocks.get(index)
485
+ } else {
486
+ if (opts && opts.wait === false) return null
487
+ if (opts && opts.onwait) opts.onwait(index)
356
488
 
357
- download (range) {
358
- const start = (range && range.start) || 0
359
- const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
360
- const linear = !!(range && range.linear)
489
+ const activeRequests = (opts && opts.activeRequests) || this.activeRequests
490
+ const req = this.replicator.addBlock(activeRequests, index)
361
491
 
362
- // TODO: support range.blocks
492
+ block = await req.promise
493
+ }
363
494
 
364
- const r = Replicator.createRange(start, end, linear)
495
+ if (this.encryption) this.encryption.decrypt(index, block)
496
+ return this._decode(encoding, block)
497
+ }
365
498
 
366
- if (this.opened) this.replicator.addRange(r)
367
- else this.opening.then(() => this.replicator.addRange(r), noop)
499
+ createReadStream (opts) {
500
+ return new ReadStream(this, opts)
501
+ }
368
502
 
369
- return r
503
+ createWriteStream (opts) {
504
+ return new WriteStream(this, opts)
370
505
  }
371
506
 
372
- // TODO: get rid of this / deprecate it?
373
- cancel (request) {
374
- // Do nothing for now
507
+ download (range) {
508
+ const reqP = this._download(range)
509
+
510
+ // do not crash in the background...
511
+ reqP.catch(noop)
512
+
513
+ // TODO: turn this into an actual object...
514
+ return {
515
+ async downloaded () {
516
+ const req = await reqP
517
+ return req.promise
518
+ },
519
+ destroy () {
520
+ reqP.then(req => req.context && req.context.detach(req), noop)
521
+ }
522
+ }
523
+ }
524
+
525
+ async _download (range) {
526
+ if (this.opened === false) await this.opening
527
+ const activeRequests = (range && range.activeRequests) || this.activeRequests
528
+ return this.replicator.addRange(activeRequests, range)
375
529
  }
376
530
 
377
531
  // TODO: get rid of this / deprecate it?
@@ -379,6 +533,11 @@ module.exports = class Hypercore extends EventEmitter {
379
533
  range.destroy(null)
380
534
  }
381
535
 
536
+ // TODO: get rid of this / deprecate it?
537
+ cancel (request) {
538
+ // Do nothing for now
539
+ }
540
+
382
541
  async truncate (newLength = 0, fork = -1) {
383
542
  if (this.opened === false) await this.opening
384
543
  if (this.writable === false) throw new Error('Core is not writable')
@@ -394,40 +553,106 @@ module.exports = class Hypercore extends EventEmitter {
394
553
  if (this.opened === false) await this.opening
395
554
  if (this.writable === false) throw new Error('Core is not writable')
396
555
 
397
- const blks = Array.isArray(blocks) ? blocks : [blocks]
398
- const buffers = new Array(blks.length)
556
+ blocks = Array.isArray(blocks) ? blocks : [blocks]
557
+
558
+ const preappend = this.encryption && this._preappend
399
559
 
400
- for (let i = 0; i < blks.length; i++) {
401
- const blk = blks[i]
560
+ const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
402
561
 
403
- const buf = Buffer.isBuffer(blk)
404
- ? blk
405
- : this.valueEncoding
406
- ? c.encode(this.valueEncoding, blk)
407
- : Buffer.from(blk)
562
+ if (this.encodeBatch === null) {
563
+ for (let i = 0; i < blocks.length; i++) {
564
+ buffers[i] = this._encode(this.valueEncoding, blocks[i])
565
+ }
566
+ }
567
+
568
+ return await this.core.append(buffers, this.sign, { preappend })
569
+ }
408
570
 
409
- buffers[i] = buf
571
+ async treeHash (length) {
572
+ if (length === undefined) {
573
+ await this.ready()
574
+ length = this.core.length
410
575
  }
411
576
 
412
- return await this.core.append(buffers, this.sign)
577
+ const roots = await this.core.tree.getRoots(length)
578
+ return this.crypto.tree(roots)
413
579
  }
414
580
 
415
- registerExtension (name, handlers) {
416
- return this.extensions.register(name, handlers)
581
+ registerExtension (name, handlers = {}) {
582
+ if (this.extensions.has(name)) {
583
+ const ext = this.extensions.get(name)
584
+ ext.handlers = handlers
585
+ ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
586
+ ext.session = this
587
+ return ext
588
+ }
589
+
590
+ const ext = {
591
+ name,
592
+ handlers,
593
+ encoding: c.from(codecs(handlers.encoding) || c.buffer),
594
+ session: this,
595
+ send (message, peer) {
596
+ const buffer = c.encode(this.encoding, message)
597
+ peer.extension(name, buffer)
598
+ },
599
+ broadcast (message) {
600
+ const buffer = c.encode(this.encoding, message)
601
+ for (const peer of this.session.peers) {
602
+ peer.extension(name, buffer)
603
+ }
604
+ },
605
+ destroy () {
606
+ for (const peer of this.session.peers) {
607
+ if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
608
+ }
609
+ this.session.extensions.delete(name)
610
+ },
611
+ _onmessage (state, peer) {
612
+ const m = this.encoding.decode(state)
613
+ if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
614
+ }
615
+ }
616
+
617
+ this.extensions.set(name, ext)
618
+ for (const peer of this.peers) {
619
+ peer.extensions.set(name, ext)
620
+ }
621
+
622
+ return ext
417
623
  }
418
624
 
419
- // called by the extensions
420
- onextensionupdate () {
421
- if (this.replicator !== null) this.replicator.broadcastOptions()
625
+ _encode (enc, val) {
626
+ const state = { start: this.padding, end: this.padding, buffer: null }
627
+
628
+ if (b4a.isBuffer(val)) {
629
+ if (state.start === 0) return val
630
+ state.end += val.byteLength
631
+ } else if (enc) {
632
+ enc.preencode(state, val)
633
+ } else {
634
+ val = b4a.from(val)
635
+ if (state.start === 0) return val
636
+ state.end += val.byteLength
637
+ }
638
+
639
+ state.buffer = b4a.allocUnsafe(state.end)
640
+
641
+ if (enc) enc.encode(state, val)
642
+ else state.buffer.set(val, state.start)
643
+
644
+ return state.buffer
645
+ }
646
+
647
+ _decode (enc, block) {
648
+ block = block.subarray(this.padding)
649
+ if (enc) return c.decode(enc, block)
650
+ return block
422
651
  }
423
652
  }
424
653
 
425
654
  function noop () {}
426
655
 
427
- function decode (enc, buf) {
428
- return enc ? c.decode(enc, buf) : buf
429
- }
430
-
431
656
  function isStream (s) {
432
657
  return typeof s === 'object' && s && typeof s.pipe === 'function'
433
658
  }
@@ -441,5 +666,14 @@ function requireMaybe (name) {
441
666
  }
442
667
 
443
668
  function toHex (buf) {
444
- return buf && buf.toString('hex')
669
+ return buf && b4a.toString(buf, 'hex')
670
+ }
671
+
672
+ function preappend (blocks) {
673
+ const offset = this.core.tree.length
674
+ const fork = this.core.tree.fork
675
+
676
+ for (let i = 0; i < blocks.length; i++) {
677
+ this.encryption.encrypt(offset + i, blocks[i], fork)
678
+ }
445
679
  }