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/README.md +83 -22
- package/index.js +587 -217
- 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 -85
- 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
|
|
@@ -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.
|
|
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.
|
|
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
|
-
this._preappend =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
139
|
-
if (!this.
|
|
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.
|
|
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
|
-
|
|
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,112 +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
|
-
|
|
290
|
-
}
|
|
526
|
+
if (s.sparse ? truncated : truncatedNonSparse) {
|
|
527
|
+
s.emit('truncate', bitfield.start, this.core.tree.fork)
|
|
528
|
+
}
|
|
291
529
|
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
536
|
+
const contig = this.core.header.contiguousLength
|
|
296
537
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
|
320
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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)
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
if (
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
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
|
|
392
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
404
|
-
return this._decode(encoding, block)
|
|
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
|
+
}
|
|
413
730
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
? range.blocks
|
|
417
|
-
: new Set(range.blocks)
|
|
731
|
+
return block
|
|
732
|
+
}
|
|
418
733
|
|
|
419
|
-
|
|
420
|
-
|
|
734
|
+
createReadStream (opts) {
|
|
735
|
+
return new ReadStream(this, opts)
|
|
736
|
+
}
|
|
421
737
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
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,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
|
|
791
|
+
if (this.writable === false) throw SESSION_NOT_WRITABLE()
|
|
460
792
|
|
|
461
793
|
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
462
794
|
|
|
463
|
-
const
|
|
795
|
+
const preappend = this.encryption && this._preappend
|
|
796
|
+
|
|
797
|
+
const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
|
|
464
798
|
|
|
465
|
-
|
|
466
|
-
|
|
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.
|
|
470
|
-
preappend: this._preappend
|
|
471
|
-
})
|
|
805
|
+
return await this.core.append(buffers, this.auth, { preappend })
|
|
472
806
|
}
|
|
473
807
|
|
|
474
|
-
|
|
475
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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
|
|
519
|
-
|
|
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 &&
|
|
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
|
+
}
|