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/README.md +83 -22
- package/index.js +583 -212
- package/lib/bitfield.js +109 -41
- package/lib/block-encryption.js +3 -2
- package/lib/block-store.js +6 -4
- package/lib/caps.js +32 -0
- package/lib/core.js +166 -35
- package/lib/errors.js +50 -0
- package/lib/info.js +23 -0
- package/lib/merkle-tree.js +181 -105
- package/lib/messages.js +249 -168
- package/lib/oplog.js +4 -3
- package/lib/remote-bitfield.js +28 -7
- package/lib/replicator.js +1415 -624
- package/lib/streams.js +56 -0
- package/package.json +20 -15
- package/.github/workflows/test-node.yml +0 -23
- package/CHANGELOG.md +0 -37
- package/UPGRADE.md +0 -9
- package/examples/announce.js +0 -19
- package/examples/basic.js +0 -10
- package/examples/http.js +0 -123
- package/examples/lookup.js +0 -20
- package/lib/extensions.js +0 -76
- package/lib/protocol.js +0 -524
- package/lib/random-iterator.js +0 -46
- package/test/basic.js +0 -90
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- package/test/encryption.js +0 -121
- package/test/extension.js +0 -71
- package/test/helpers/index.js +0 -23
- package/test/merkle-tree.js +0 -518
- package/test/mutex.js +0 -137
- package/test/oplog.js +0 -399
- package/test/preload.js +0 -72
- package/test/replicate.js +0 -372
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
package/index.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
const { EventEmitter } = require('events')
|
|
2
|
-
const
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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.
|
|
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 =
|
|
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(
|
|
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 + '
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
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')
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
202
|
+
sparse,
|
|
203
|
+
wait,
|
|
204
|
+
onwait,
|
|
127
205
|
_opening: this.opening,
|
|
128
206
|
_sessions: this.sessions
|
|
129
207
|
})
|
|
130
208
|
|
|
131
|
-
s.
|
|
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
|
-
|
|
138
|
-
if (!this.
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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.
|
|
436
|
+
this.replicator.attachTo(mux)
|
|
203
437
|
} else {
|
|
204
|
-
this.opening.then(() => this.replicator.
|
|
438
|
+
this.opening.then(() => this.replicator.attachTo(mux), mux.destroy.bind(mux))
|
|
205
439
|
}
|
|
206
440
|
|
|
207
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
onupdate: this._oncoreupdate.bind(this)
|
|
269
|
-
})
|
|
508
|
+
if (truncated) {
|
|
509
|
+
this.replicator.ontruncate(bitfield.start)
|
|
510
|
+
}
|
|
270
511
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
await this.core.userData(key, value)
|
|
512
|
+
if ((status & 0b0011) !== 0) {
|
|
513
|
+
this.replicator.onupgrade()
|
|
274
514
|
}
|
|
275
|
-
}
|
|
276
515
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
})
|
|
516
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
517
|
+
const s = this.sessions[i]
|
|
280
518
|
|
|
281
|
-
|
|
519
|
+
if (truncated) {
|
|
520
|
+
if (s.cache) s.cache.clear()
|
|
282
521
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
526
|
+
if (s.sparse ? truncated : truncatedNonSparse) {
|
|
527
|
+
s.emit('truncate', bitfield.start, this.core.tree.fork)
|
|
528
|
+
}
|
|
290
529
|
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
536
|
+
const contig = this.core.header.contiguousLength
|
|
295
537
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
304
|
-
|
|
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
|
|
319
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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)
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
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
|
|
391
|
-
|
|
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 =
|
|
397
|
-
|
|
705
|
+
block = this.core.blocks.get(index)
|
|
706
|
+
|
|
707
|
+
if (this.cache) this.cache.set(index, block)
|
|
398
708
|
} else {
|
|
399
|
-
if (opts && opts.
|
|
400
|
-
|
|
401
|
-
|
|
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
|
|
721
|
+
return block
|
|
405
722
|
}
|
|
406
723
|
|
|
407
|
-
|
|
408
|
-
const
|
|
724
|
+
async _cacheOnResolve (index, req, fork) {
|
|
725
|
+
const block = await req
|
|
409
726
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
: new Set(range.blocks)
|
|
734
|
+
createReadStream (opts) {
|
|
735
|
+
return new ReadStream(this, opts)
|
|
736
|
+
}
|
|
418
737
|
|
|
419
|
-
|
|
420
|
-
|
|
738
|
+
createWriteStream (opts) {
|
|
739
|
+
return new WriteStream(this, opts)
|
|
740
|
+
}
|
|
421
741
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
760
|
+
async _download (range) {
|
|
761
|
+
if (this.opened === false) await this.opening
|
|
429
762
|
|
|
430
|
-
|
|
431
|
-
else this.opening.then(() => this.replicator.addRange(r), noop)
|
|
763
|
+
const activeRequests = (range && range.activeRequests) || this.activeRequests
|
|
432
764
|
|
|
433
|
-
return
|
|
765
|
+
return this.replicator.addRange(activeRequests, range)
|
|
434
766
|
}
|
|
435
767
|
|
|
436
768
|
// TODO: get rid of this / deprecate it?
|
|
437
|
-
|
|
438
|
-
|
|
769
|
+
undownload (range) {
|
|
770
|
+
range.destroy(null)
|
|
439
771
|
}
|
|
440
772
|
|
|
441
773
|
// TODO: get rid of this / deprecate it?
|
|
442
|
-
|
|
443
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
467
|
-
|
|
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.
|
|
805
|
+
return await this.core.append(buffers, this.auth, { preappend })
|
|
471
806
|
}
|
|
472
807
|
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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
|
|
518
|
-
|
|
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 &&
|
|
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
|
+
}
|