hypercore 10.0.0-alpha.4 → 10.0.0-alpha.40
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 +58 -3
- package/index.js +504 -166
- package/lib/bitfield.js +9 -5
- package/lib/block-encryption.js +68 -0
- package/lib/block-store.js +3 -1
- package/lib/caps.js +34 -0
- package/lib/core.js +84 -27
- package/lib/errors.js +42 -0
- package/lib/merkle-tree.js +186 -113
- package/lib/messages.js +249 -168
- package/lib/oplog.js +4 -3
- package/lib/replicator.js +1288 -548
- package/lib/streams.js +56 -0
- package/package.json +18 -9
- 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 -78
- package/test/bitfield.js +0 -71
- package/test/core.js +0 -290
- package/test/encodings.js +0 -18
- 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 -333
- package/test/sessions.js +0 -173
- package/test/user-data.js +0 -47
package/index.js
CHANGED
|
@@ -3,15 +3,19 @@ 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')
|
|
9
|
+
const Protomux = require('protomux')
|
|
8
10
|
const codecs = require('codecs')
|
|
9
11
|
|
|
10
12
|
const fsctl = requireMaybe('fsctl') || { lock: noop, sparse: noop }
|
|
11
13
|
|
|
12
14
|
const Replicator = require('./lib/replicator')
|
|
13
|
-
const Extensions = require('./lib/extensions')
|
|
14
15
|
const Core = require('./lib/core')
|
|
16
|
+
const BlockEncryption = require('./lib/block-encryption')
|
|
17
|
+
const { ReadStream, WriteStream } = require('./lib/streams')
|
|
18
|
+
const { BAD_ARGUMENT, SESSION_CLOSED, SESSION_NOT_WRITABLE, SNAPSHOT_NOT_AVAILABLE } = require('./lib/errors')
|
|
15
19
|
|
|
16
20
|
const promises = Symbol.for('hypercore.promises')
|
|
17
21
|
const inspect = Symbol.for('nodejs.util.inspect.custom')
|
|
@@ -28,14 +32,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
28
32
|
opts = key
|
|
29
33
|
key = null
|
|
30
34
|
}
|
|
35
|
+
|
|
31
36
|
if (key && typeof key === 'string') {
|
|
32
|
-
key =
|
|
33
|
-
}
|
|
34
|
-
if (key && key.byteLength !== 32) {
|
|
35
|
-
throw new Error('Hypercore key should be 32 bytes')
|
|
37
|
+
key = b4a.from(key, 'hex')
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
if (!opts) opts = {}
|
|
41
|
+
|
|
42
|
+
if (!opts.crypto && key && key.byteLength !== 32) {
|
|
43
|
+
throw BAD_ARGUMENT('Hypercore key should be 32 bytes')
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
if (!storage) storage = opts.storage
|
|
40
47
|
|
|
41
48
|
this[promises] = true
|
|
@@ -44,23 +51,32 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
44
51
|
this.crypto = opts.crypto || hypercoreCrypto
|
|
45
52
|
this.core = null
|
|
46
53
|
this.replicator = null
|
|
47
|
-
this.
|
|
54
|
+
this.encryption = null
|
|
55
|
+
this.extensions = new Map()
|
|
48
56
|
this.cache = opts.cache === true ? new Xache({ maxSize: 65536, maxAge: 0 }) : (opts.cache || null)
|
|
49
57
|
|
|
50
58
|
this.valueEncoding = null
|
|
59
|
+
this.encodeBatch = null
|
|
60
|
+
this.activeRequests = []
|
|
61
|
+
|
|
51
62
|
this.key = key || null
|
|
52
|
-
this.
|
|
63
|
+
this.keyPair = null
|
|
53
64
|
this.readable = true
|
|
54
65
|
this.writable = false
|
|
55
66
|
this.opened = false
|
|
56
67
|
this.closed = false
|
|
68
|
+
this.snapshotted = !!opts.snapshot
|
|
57
69
|
this.sessions = opts._sessions || [this]
|
|
58
|
-
this.
|
|
70
|
+
this.auth = opts.auth || null
|
|
59
71
|
this.autoClose = !!opts.autoClose
|
|
60
72
|
|
|
61
73
|
this.closing = null
|
|
62
|
-
this.opening =
|
|
74
|
+
this.opening = this._openSession(key, storage, opts)
|
|
63
75
|
this.opening.catch(noop)
|
|
76
|
+
|
|
77
|
+
this._preappend = preappend.bind(this)
|
|
78
|
+
this._snapshot = null
|
|
79
|
+
this._findingPeers = 0
|
|
64
80
|
}
|
|
65
81
|
|
|
66
82
|
[inspect] (depth, opts) {
|
|
@@ -69,21 +85,78 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
69
85
|
while (indent.length < opts.indentationLvl) indent += ' '
|
|
70
86
|
}
|
|
71
87
|
|
|
88
|
+
let peers = ''
|
|
89
|
+
const min = Math.min(this.peers.length, 5)
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < min; i++) {
|
|
92
|
+
const peer = this.peers[i]
|
|
93
|
+
|
|
94
|
+
peers += indent + ' Peer(\n'
|
|
95
|
+
peers += indent + ' remotePublicKey: ' + opts.stylize(toHex(peer.remotePublicKey), 'string') + '\n'
|
|
96
|
+
peers += indent + ' remoteLength: ' + opts.stylize(peer.remoteLength, 'number') + '\n'
|
|
97
|
+
peers += indent + ' remoteFork: ' + opts.stylize(peer.remoteFork, 'number') + '\n'
|
|
98
|
+
peers += indent + ' remoteCanUpgrade: ' + opts.stylize(peer.remoteCanUpgrade, 'boolean') + '\n'
|
|
99
|
+
peers += indent + ' )' + '\n'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.peers.length > 5) {
|
|
103
|
+
peers += indent + ' ... and ' + (this.peers.length - 5) + ' more\n'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (peers) peers = '[\n' + peers + indent + ' ]'
|
|
107
|
+
else peers = '[ ' + opts.stylize(0, 'number') + ' ]'
|
|
108
|
+
|
|
72
109
|
return this.constructor.name + '(\n' +
|
|
73
|
-
indent + ' key: ' + opts.stylize(
|
|
110
|
+
indent + ' key: ' + opts.stylize(toHex(this.key), 'string') + '\n' +
|
|
74
111
|
indent + ' discoveryKey: ' + opts.stylize(toHex(this.discoveryKey), 'string') + '\n' +
|
|
75
112
|
indent + ' opened: ' + opts.stylize(this.opened, 'boolean') + '\n' +
|
|
113
|
+
indent + ' closed: ' + opts.stylize(this.closed, 'boolean') + '\n' +
|
|
114
|
+
indent + ' snapshotted: ' + opts.stylize(this.snapshotted, 'boolean') + '\n' +
|
|
76
115
|
indent + ' writable: ' + opts.stylize(this.writable, 'boolean') + '\n' +
|
|
77
|
-
indent + ' sessions: ' + opts.stylize(this.sessions.length, 'number') + '\n' +
|
|
78
|
-
indent + ' peers: [ ' + opts.stylize(this.peers.length, 'number') + ' ]\n' +
|
|
79
116
|
indent + ' length: ' + opts.stylize(this.length, 'number') + '\n' +
|
|
80
117
|
indent + ' byteLength: ' + opts.stylize(this.byteLength, 'number') + '\n' +
|
|
118
|
+
indent + ' fork: ' + opts.stylize(this.fork, 'number') + '\n' +
|
|
119
|
+
indent + ' sessions: [ ' + opts.stylize(this.sessions.length, 'number') + ' ]\n' +
|
|
120
|
+
indent + ' activeRequests: [ ' + opts.stylize(this.activeRequests.length, 'number') + ' ]\n' +
|
|
121
|
+
indent + ' peers: ' + peers + '\n' +
|
|
81
122
|
indent + ')'
|
|
82
123
|
}
|
|
83
124
|
|
|
84
|
-
static
|
|
85
|
-
|
|
86
|
-
|
|
125
|
+
static getProtocolMuxer (stream) {
|
|
126
|
+
return stream.noiseStream.userData
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
static createProtocolStream (isInitiator, opts = {}) {
|
|
130
|
+
let outerStream = Protomux.isProtomux(isInitiator)
|
|
131
|
+
? isInitiator.stream
|
|
132
|
+
: isStream(isInitiator)
|
|
133
|
+
? isInitiator
|
|
134
|
+
: opts.stream
|
|
135
|
+
|
|
136
|
+
let noiseStream = null
|
|
137
|
+
|
|
138
|
+
if (outerStream) {
|
|
139
|
+
noiseStream = outerStream.noiseStream
|
|
140
|
+
} else {
|
|
141
|
+
noiseStream = new NoiseSecretStream(isInitiator, null, opts)
|
|
142
|
+
outerStream = noiseStream.rawStream
|
|
143
|
+
}
|
|
144
|
+
if (!noiseStream) throw BAD_ARGUMENT('Invalid stream')
|
|
145
|
+
|
|
146
|
+
if (!noiseStream.userData) {
|
|
147
|
+
const protocol = new Protomux(noiseStream)
|
|
148
|
+
|
|
149
|
+
if (opts.ondiscoverykey) {
|
|
150
|
+
protocol.pair({ protocol: 'hypercore/alpha' }, opts.ondiscoverykey)
|
|
151
|
+
}
|
|
152
|
+
if (opts.keepAlive !== false) {
|
|
153
|
+
noiseStream.setKeepAlive(5000)
|
|
154
|
+
noiseStream.setTimeout(10000)
|
|
155
|
+
}
|
|
156
|
+
noiseStream.userData = protocol
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return outerStream
|
|
87
160
|
}
|
|
88
161
|
|
|
89
162
|
static defaultStorage (storage, opts = {}) {
|
|
@@ -98,45 +171,168 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
98
171
|
}
|
|
99
172
|
}
|
|
100
173
|
|
|
174
|
+
snapshot (opts) {
|
|
175
|
+
return this.session({ ...opts, snapshot: true })
|
|
176
|
+
}
|
|
177
|
+
|
|
101
178
|
session (opts = {}) {
|
|
102
179
|
if (this.closing) {
|
|
103
180
|
// This makes the closing logic alot easier. If this turns out to be a problem
|
|
104
181
|
// in practive, open an issue and we'll try to make a solution for it.
|
|
105
|
-
throw
|
|
182
|
+
throw SESSION_CLOSED('Cannot make sessions on a closing core')
|
|
106
183
|
}
|
|
107
184
|
|
|
108
185
|
const Clz = opts.class || Hypercore
|
|
109
|
-
const keyPair = opts.keyPair && opts.keyPair.secretKey && { ...opts.keyPair }
|
|
110
|
-
|
|
111
|
-
// This only works if the hypercore was fully loaded,
|
|
112
|
-
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
113
|
-
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
114
|
-
|
|
115
186
|
const s = new Clz(this.storage, this.key, {
|
|
116
187
|
...opts,
|
|
117
|
-
sign: opts.sign || (keyPair && keyPair.secretKey && Core.createSigner(this.crypto, keyPair)) || this.sign,
|
|
118
|
-
valueEncoding: this.valueEncoding,
|
|
119
|
-
extensions: this.extensions,
|
|
120
188
|
_opening: this.opening,
|
|
121
189
|
_sessions: this.sessions
|
|
122
190
|
})
|
|
123
191
|
|
|
124
|
-
s.
|
|
192
|
+
s._passCapabilities(this)
|
|
193
|
+
|
|
194
|
+
if (opts.encryptionKey) {
|
|
195
|
+
// Only override the block encryption if its either not already set or if
|
|
196
|
+
// the caller provided a different key.
|
|
197
|
+
if (
|
|
198
|
+
!this.encryption ||
|
|
199
|
+
!b4a.equals(this.encryption.key, opts.encryptionKey)
|
|
200
|
+
) {
|
|
201
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
125
205
|
this.sessions.push(s)
|
|
126
206
|
|
|
127
207
|
return s
|
|
128
208
|
}
|
|
129
209
|
|
|
130
|
-
|
|
131
|
-
if (!this.
|
|
210
|
+
_passCapabilities (o) {
|
|
211
|
+
if (!this.auth) this.auth = o.auth
|
|
212
|
+
|
|
132
213
|
this.crypto = o.crypto
|
|
133
|
-
this.opened = o.opened
|
|
134
214
|
this.key = o.key
|
|
135
|
-
this.discoveryKey = o.discoveryKey
|
|
136
215
|
this.core = o.core
|
|
137
216
|
this.replicator = o.replicator
|
|
138
|
-
this.
|
|
217
|
+
this.encryption = o.encryption
|
|
218
|
+
this.writable = !!(this.auth && this.auth.sign)
|
|
139
219
|
this.autoClose = o.autoClose
|
|
220
|
+
|
|
221
|
+
if (this.snapshotted && this.core && !this._snapshot) this._updateSnapshot()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async _openFromExisting (from, opts) {
|
|
225
|
+
await from.opening
|
|
226
|
+
|
|
227
|
+
this._passCapabilities(from)
|
|
228
|
+
this.sessions = from.sessions
|
|
229
|
+
this.storage = from.storage
|
|
230
|
+
this.replicator.findingPeers += this._findingPeers
|
|
231
|
+
|
|
232
|
+
this.sessions.push(this)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async _openSession (key, storage, opts) {
|
|
236
|
+
const isFirst = !opts._opening
|
|
237
|
+
|
|
238
|
+
if (!isFirst) await opts._opening
|
|
239
|
+
if (opts.preload) opts = { ...opts, ...(await opts.preload()) }
|
|
240
|
+
|
|
241
|
+
const keyPair = (key && opts.keyPair)
|
|
242
|
+
? { ...opts.keyPair, publicKey: key }
|
|
243
|
+
: key
|
|
244
|
+
? { publicKey: key, secretKey: null }
|
|
245
|
+
: opts.keyPair
|
|
246
|
+
|
|
247
|
+
// This only works if the hypercore was fully loaded,
|
|
248
|
+
// but we only do this to validate the keypair to help catch bugs so yolo
|
|
249
|
+
if (this.key && keyPair) keyPair.publicKey = this.key
|
|
250
|
+
|
|
251
|
+
if (opts.auth) {
|
|
252
|
+
this.auth = opts.auth
|
|
253
|
+
} else if (opts.sign) {
|
|
254
|
+
this.auth = Core.createAuth(this.crypto, keyPair, opts)
|
|
255
|
+
} else if (keyPair && keyPair.secretKey) {
|
|
256
|
+
this.auth = Core.createAuth(this.crypto, keyPair)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (isFirst) {
|
|
260
|
+
await this._openCapabilities(keyPair, storage, opts)
|
|
261
|
+
// Only the root session should pass capabilities to other sessions.
|
|
262
|
+
for (let i = 0; i < this.sessions.length; i++) {
|
|
263
|
+
const s = this.sessions[i]
|
|
264
|
+
if (s !== this) s._passCapabilities(this)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!this.auth) this.auth = this.core.defaultAuth
|
|
269
|
+
this.writable = !!this.auth.sign
|
|
270
|
+
|
|
271
|
+
if (opts.valueEncoding) {
|
|
272
|
+
this.valueEncoding = c.from(codecs(opts.valueEncoding))
|
|
273
|
+
}
|
|
274
|
+
if (opts.encodeBatch) {
|
|
275
|
+
this.encodeBatch = opts.encodeBatch
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// This is a hidden option that's only used by Corestore.
|
|
279
|
+
// It's required so that corestore can load a name from userData before 'ready' is emitted.
|
|
280
|
+
if (opts._preready) await opts._preready(this)
|
|
281
|
+
|
|
282
|
+
this.opened = true
|
|
283
|
+
this.emit('ready')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async _openCapabilities (keyPair, storage, opts) {
|
|
287
|
+
if (opts.from) return this._openFromExisting(opts.from, opts)
|
|
288
|
+
|
|
289
|
+
this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
290
|
+
|
|
291
|
+
this.core = await Core.open(this.storage, {
|
|
292
|
+
force: opts.force,
|
|
293
|
+
createIfMissing: opts.createIfMissing,
|
|
294
|
+
overwrite: opts.overwrite,
|
|
295
|
+
keyPair,
|
|
296
|
+
crypto: this.crypto,
|
|
297
|
+
legacy: opts.legacy,
|
|
298
|
+
auth: opts.auth,
|
|
299
|
+
onupdate: this._oncoreupdate.bind(this)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
if (opts.userData) {
|
|
303
|
+
for (const [key, value] of Object.entries(opts.userData)) {
|
|
304
|
+
await this.core.userData(key, value)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.key = this.core.header.signer.publicKey
|
|
309
|
+
this.keyPair = this.core.header.signer
|
|
310
|
+
|
|
311
|
+
this.replicator = new Replicator(this.core, this.key, {
|
|
312
|
+
eagerUpdate: true,
|
|
313
|
+
allowFork: opts.allowFork !== false,
|
|
314
|
+
onpeerupdate: this._onpeerupdate.bind(this),
|
|
315
|
+
onupload: this._onupload.bind(this)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
this.replicator.findingPeers += this._findingPeers
|
|
319
|
+
|
|
320
|
+
if (!this.encryption && opts.encryptionKey) {
|
|
321
|
+
this.encryption = new BlockEncryption(opts.encryptionKey, this.key)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
_updateSnapshot () {
|
|
326
|
+
const prev = this._snapshot
|
|
327
|
+
const next = this._snapshot = {
|
|
328
|
+
length: this.core.tree.length,
|
|
329
|
+
byteLength: this.core.tree.byteLength,
|
|
330
|
+
fork: this.core.tree.fork,
|
|
331
|
+
compatLength: this.core.tree.length
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!prev) return true
|
|
335
|
+
return prev.length !== next.length || prev.fork !== next.fork
|
|
140
336
|
}
|
|
141
337
|
|
|
142
338
|
close () {
|
|
@@ -155,6 +351,20 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
155
351
|
this.readable = false
|
|
156
352
|
this.writable = false
|
|
157
353
|
this.closed = true
|
|
354
|
+
this.opened = false
|
|
355
|
+
|
|
356
|
+
const gc = []
|
|
357
|
+
for (const ext of this.extensions.values()) {
|
|
358
|
+
if (ext.session === this) gc.push(ext)
|
|
359
|
+
}
|
|
360
|
+
for (const ext of gc) ext.destroy()
|
|
361
|
+
|
|
362
|
+
if (this.replicator !== null) {
|
|
363
|
+
this.replicator.findingPeers -= this._findingPeers
|
|
364
|
+
this.replicator.clearRequests(this.activeRequests)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
this._findingPeers = 0
|
|
158
368
|
|
|
159
369
|
if (this.sessions.length) {
|
|
160
370
|
// if this is the last session and we are auto closing, trigger that first to enforce error handling
|
|
@@ -170,41 +380,54 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
170
380
|
}
|
|
171
381
|
|
|
172
382
|
replicate (isInitiator, opts = {}) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let noiseStream = null
|
|
383
|
+
// Only limitation here is that ondiscoverykey doesn't work atm when passing a muxer directly,
|
|
384
|
+
// because it doesn't really make a lot of sense.
|
|
385
|
+
if (Protomux.isProtomux(isInitiator)) return this._attachToMuxer(isInitiator, opts)
|
|
177
386
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
outerStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
182
|
-
noiseStream = outerStream.noiseStream
|
|
183
|
-
}
|
|
184
|
-
if (!noiseStream) throw new Error('Invalid stream passed to replicate')
|
|
387
|
+
const protocolStream = Hypercore.createProtocolStream(isInitiator, opts)
|
|
388
|
+
const noiseStream = protocolStream.noiseStream
|
|
389
|
+
const protocol = noiseStream.userData
|
|
185
390
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
391
|
+
this._attachToMuxer(protocol, opts)
|
|
392
|
+
|
|
393
|
+
return protocolStream
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
_attachToMuxer (mux, opts) {
|
|
397
|
+
// If the user wants to, we can make this replication run in a session
|
|
398
|
+
// that way the core wont close "under them" during replication
|
|
399
|
+
if (opts.session) {
|
|
400
|
+
const s = this.session()
|
|
401
|
+
mux.stream.on('close', () => s.close().catch(noop))
|
|
190
402
|
}
|
|
191
403
|
|
|
192
|
-
const protocol = noiseStream.userData
|
|
193
404
|
if (this.opened) {
|
|
194
|
-
this.replicator.
|
|
405
|
+
this.replicator.attachTo(mux)
|
|
195
406
|
} else {
|
|
196
|
-
this.opening.then(() => this.replicator.
|
|
407
|
+
this.opening.then(() => this.replicator.attachTo(mux), mux.destroy.bind(mux))
|
|
197
408
|
}
|
|
198
409
|
|
|
199
|
-
return
|
|
410
|
+
return mux
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
get discoveryKey () {
|
|
414
|
+
return this.replicator === null ? null : this.replicator.discoveryKey
|
|
200
415
|
}
|
|
201
416
|
|
|
202
417
|
get length () {
|
|
203
|
-
return this.
|
|
418
|
+
return this._snapshot
|
|
419
|
+
? this._snapshot.length
|
|
420
|
+
: (this.core === null ? 0 : this.core.tree.length)
|
|
204
421
|
}
|
|
205
422
|
|
|
206
423
|
get byteLength () {
|
|
207
|
-
return this.
|
|
424
|
+
return this._snapshot
|
|
425
|
+
? this._snapshot.byteLength
|
|
426
|
+
: (this.core === null ? 0 : this.core.tree.byteLength - (this.core.tree.length * this.padding))
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
get contiguousLength () {
|
|
430
|
+
return this.core === null ? 0 : this.core.header.contiguousLength
|
|
208
431
|
}
|
|
209
432
|
|
|
210
433
|
get fork () {
|
|
@@ -215,105 +438,77 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
215
438
|
return this.replicator === null ? [] : this.replicator.peers
|
|
216
439
|
}
|
|
217
440
|
|
|
218
|
-
|
|
219
|
-
return this.
|
|
441
|
+
get encryptionKey () {
|
|
442
|
+
return this.encryption && this.encryption.key
|
|
220
443
|
}
|
|
221
444
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.valueEncoding = opts.valueEncoding ? c.from(codecs(opts.valueEncoding)) : null
|
|
226
|
-
|
|
227
|
-
const keyPair = (key && opts.keyPair)
|
|
228
|
-
? { ...opts.keyPair, publicKey: key }
|
|
229
|
-
: key
|
|
230
|
-
? { publicKey: key, secretKey: null }
|
|
231
|
-
: opts.keyPair
|
|
232
|
-
|
|
233
|
-
if (opts.from) {
|
|
234
|
-
const from = opts.from
|
|
235
|
-
await from.opening
|
|
236
|
-
for (const [name, ext] of this.extensions) from.extensions.register(name, null, ext)
|
|
237
|
-
this._initSession(from)
|
|
238
|
-
this.extensions = from.extensions
|
|
239
|
-
this.sessions = from.sessions
|
|
240
|
-
this.storage = from.storage
|
|
241
|
-
if (!this.sign) this.sign = opts.sign || ((keyPair && keyPair.secretKey) ? Core.createSigner(this.crypto, keyPair) : null)
|
|
242
|
-
this.writable = !!this.sign
|
|
243
|
-
this.sessions.push(this)
|
|
244
|
-
return
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!this.storage) this.storage = Hypercore.defaultStorage(opts.storage || storage)
|
|
248
|
-
|
|
249
|
-
this.core = await Core.open(this.storage, {
|
|
250
|
-
keyPair,
|
|
251
|
-
crypto: this.crypto,
|
|
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.replicator = new Replicator(this.core, {
|
|
262
|
-
onupdate: this._onpeerupdate.bind(this)
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
if (!this.sign) this.sign = opts.sign || this.core.defaultSign
|
|
266
|
-
|
|
267
|
-
this.discoveryKey = this.crypto.discoveryKey(this.core.header.signer.publicKey)
|
|
268
|
-
this.key = this.core.header.signer.publicKey
|
|
269
|
-
this.writable = !!this.sign
|
|
445
|
+
get padding () {
|
|
446
|
+
return this.encryption === null ? 0 : this.encryption.padding
|
|
447
|
+
}
|
|
270
448
|
|
|
271
|
-
|
|
272
|
-
this.
|
|
449
|
+
ready () {
|
|
450
|
+
return this.opening
|
|
451
|
+
}
|
|
273
452
|
|
|
274
|
-
|
|
453
|
+
_onupload (index, value, from) {
|
|
454
|
+
const byteLength = value.byteLength - this.padding
|
|
275
455
|
|
|
276
456
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
277
|
-
|
|
278
|
-
if (s !== this) s._initSession(this)
|
|
279
|
-
s.emit('ready')
|
|
457
|
+
this.sessions[i].emit('upload', index, byteLength, from)
|
|
280
458
|
}
|
|
281
459
|
}
|
|
282
460
|
|
|
283
461
|
_oncoreupdate (status, bitfield, value, from) {
|
|
284
462
|
if (status !== 0) {
|
|
463
|
+
const truncated = (status & 0b10) !== 0
|
|
464
|
+
const appended = (status & 0b01) !== 0
|
|
465
|
+
|
|
466
|
+
if (truncated) {
|
|
467
|
+
this.replicator.ontruncate(bitfield.start)
|
|
468
|
+
}
|
|
469
|
+
|
|
285
470
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
471
|
+
const s = this.sessions[i]
|
|
472
|
+
|
|
473
|
+
if (truncated) {
|
|
474
|
+
if (s.cache) s.cache.clear()
|
|
475
|
+
s.emit('truncate', bitfield.start, this.core.tree.fork)
|
|
476
|
+
// If snapshotted, make sure to update our compat so we can fail gets
|
|
477
|
+
if (s._snapshot && bitfield.start < s._snapshot.compatLength) s._snapshot.compatLength = bitfield.start
|
|
289
478
|
}
|
|
290
|
-
|
|
291
|
-
|
|
479
|
+
|
|
480
|
+
if (appended) {
|
|
481
|
+
s.emit('append')
|
|
292
482
|
}
|
|
293
483
|
}
|
|
294
484
|
|
|
295
|
-
this.replicator.
|
|
485
|
+
this.replicator.onupgrade()
|
|
296
486
|
}
|
|
297
487
|
|
|
298
|
-
if (bitfield
|
|
299
|
-
|
|
300
|
-
this.replicator.broadcastBlock(bitfield.start + i)
|
|
301
|
-
}
|
|
488
|
+
if (bitfield) {
|
|
489
|
+
this.replicator.onhave(bitfield.start, bitfield.length, bitfield.drop)
|
|
302
490
|
}
|
|
303
491
|
|
|
304
492
|
if (value) {
|
|
493
|
+
const byteLength = value.byteLength - this.padding
|
|
494
|
+
|
|
305
495
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
306
|
-
this.sessions[i].emit('download', bitfield.start,
|
|
496
|
+
this.sessions[i].emit('download', bitfield.start, byteLength, from)
|
|
307
497
|
}
|
|
308
498
|
}
|
|
309
499
|
}
|
|
310
500
|
|
|
311
501
|
_onpeerupdate (added, peer) {
|
|
312
|
-
if (added) this.extensions.update(peer)
|
|
313
502
|
const name = added ? 'peer-add' : 'peer-remove'
|
|
314
503
|
|
|
315
504
|
for (let i = 0; i < this.sessions.length; i++) {
|
|
316
505
|
this.sessions[i].emit(name, peer)
|
|
506
|
+
|
|
507
|
+
if (added) {
|
|
508
|
+
for (const ext of this.sessions[i].extensions.values()) {
|
|
509
|
+
peer.extensions.set(ext.name, ext)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
317
512
|
}
|
|
318
513
|
}
|
|
319
514
|
|
|
@@ -330,19 +525,55 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
330
525
|
return null
|
|
331
526
|
}
|
|
332
527
|
|
|
333
|
-
|
|
528
|
+
findingPeers () {
|
|
529
|
+
this._findingPeers++
|
|
530
|
+
if (this.replicator !== null && !this.closing) this.replicator.findingPeers++
|
|
531
|
+
|
|
532
|
+
let once = true
|
|
533
|
+
|
|
534
|
+
return () => {
|
|
535
|
+
if (this.closing || !once) return
|
|
536
|
+
once = false
|
|
537
|
+
this._findingPeers--
|
|
538
|
+
if (this.replicator !== null && --this.replicator.findingPeers === 0) {
|
|
539
|
+
this.replicator.updateAll()
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async update (opts) {
|
|
334
545
|
if (this.opened === false) await this.opening
|
|
546
|
+
if (this.closing !== null) return false
|
|
547
|
+
|
|
335
548
|
// TODO: add an option where a writer can bootstrap it's state from the network also
|
|
336
|
-
if (this.writable)
|
|
337
|
-
|
|
549
|
+
if (this.writable) {
|
|
550
|
+
if (!this.snapshotted) return false
|
|
551
|
+
return this._updateSnapshot()
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
555
|
+
const req = this.replicator.addUpgrade(activeRequests)
|
|
556
|
+
|
|
557
|
+
if (!this.snapshotted) return req.promise
|
|
558
|
+
if (!(await req.promise)) return false
|
|
559
|
+
|
|
560
|
+
return this._updateSnapshot()
|
|
338
561
|
}
|
|
339
562
|
|
|
340
|
-
async seek (bytes) {
|
|
563
|
+
async seek (bytes, opts) {
|
|
341
564
|
if (this.opened === false) await this.opening
|
|
342
565
|
|
|
343
|
-
const s = this.core.tree.seek(bytes)
|
|
566
|
+
const s = this.core.tree.seek(bytes, this.padding)
|
|
567
|
+
|
|
568
|
+
const offset = await s.update()
|
|
569
|
+
if (offset) return offset
|
|
570
|
+
|
|
571
|
+
if (this.closing !== null) throw SESSION_CLOSED()
|
|
344
572
|
|
|
345
|
-
|
|
573
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
574
|
+
const req = this.replicator.addSeek(activeRequests, s)
|
|
575
|
+
|
|
576
|
+
return req.promise
|
|
346
577
|
}
|
|
347
578
|
|
|
348
579
|
async has (index) {
|
|
@@ -353,6 +584,9 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
353
584
|
|
|
354
585
|
async get (index, opts) {
|
|
355
586
|
if (this.opened === false) await this.opening
|
|
587
|
+
if (this.closing !== null) throw SESSION_CLOSED()
|
|
588
|
+
if (this._snapshot !== null && index >= this._snapshot.compatLength) throw SNAPSHOT_NOT_AVAILABLE()
|
|
589
|
+
|
|
356
590
|
const c = this.cache && this.cache.get(index)
|
|
357
591
|
if (c) return c
|
|
358
592
|
const fork = this.core.tree.fork
|
|
@@ -364,30 +598,54 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
364
598
|
async _get (index, opts) {
|
|
365
599
|
const encoding = (opts && opts.valueEncoding && c.from(codecs(opts.valueEncoding))) || this.valueEncoding
|
|
366
600
|
|
|
367
|
-
|
|
368
|
-
if (opts && opts.onwait) opts.onwait(index)
|
|
601
|
+
let block
|
|
369
602
|
|
|
370
|
-
|
|
371
|
-
|
|
603
|
+
if (this.core.bitfield.get(index)) {
|
|
604
|
+
block = await this.core.blocks.get(index)
|
|
605
|
+
} else {
|
|
606
|
+
if (opts && opts.wait === false) return null
|
|
607
|
+
if (opts && opts.onwait) opts.onwait(index)
|
|
372
608
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
const end = typeof (range && range.end) === 'number' ? range.end : -1 // download all
|
|
376
|
-
const linear = !!(range && range.linear)
|
|
609
|
+
const activeRequests = (opts && opts.activeRequests) || this.activeRequests
|
|
610
|
+
const req = this.replicator.addBlock(activeRequests, index)
|
|
377
611
|
|
|
378
|
-
|
|
612
|
+
block = await req.promise
|
|
613
|
+
}
|
|
379
614
|
|
|
380
|
-
|
|
615
|
+
if (this.encryption) this.encryption.decrypt(index, block)
|
|
616
|
+
return this._decode(encoding, block)
|
|
617
|
+
}
|
|
381
618
|
|
|
382
|
-
|
|
383
|
-
|
|
619
|
+
createReadStream (opts) {
|
|
620
|
+
return new ReadStream(this, opts)
|
|
621
|
+
}
|
|
384
622
|
|
|
385
|
-
|
|
623
|
+
createWriteStream (opts) {
|
|
624
|
+
return new WriteStream(this, opts)
|
|
386
625
|
}
|
|
387
626
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
627
|
+
download (range) {
|
|
628
|
+
const reqP = this._download(range)
|
|
629
|
+
|
|
630
|
+
// do not crash in the background...
|
|
631
|
+
reqP.catch(noop)
|
|
632
|
+
|
|
633
|
+
// TODO: turn this into an actual object...
|
|
634
|
+
return {
|
|
635
|
+
async downloaded () {
|
|
636
|
+
const req = await reqP
|
|
637
|
+
return req.promise
|
|
638
|
+
},
|
|
639
|
+
destroy () {
|
|
640
|
+
reqP.then(req => req.context && req.context.detach(req), noop)
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async _download (range) {
|
|
646
|
+
if (this.opened === false) await this.opening
|
|
647
|
+
const activeRequests = (range && range.activeRequests) || this.activeRequests
|
|
648
|
+
return this.replicator.addRange(activeRequests, range)
|
|
391
649
|
}
|
|
392
650
|
|
|
393
651
|
// TODO: get rid of this / deprecate it?
|
|
@@ -395,12 +653,17 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
395
653
|
range.destroy(null)
|
|
396
654
|
}
|
|
397
655
|
|
|
656
|
+
// TODO: get rid of this / deprecate it?
|
|
657
|
+
cancel (request) {
|
|
658
|
+
// Do nothing for now
|
|
659
|
+
}
|
|
660
|
+
|
|
398
661
|
async truncate (newLength = 0, fork = -1) {
|
|
399
662
|
if (this.opened === false) await this.opening
|
|
400
|
-
if (this.writable === false) throw
|
|
663
|
+
if (this.writable === false) throw SESSION_NOT_WRITABLE()
|
|
401
664
|
|
|
402
665
|
if (fork === -1) fork = this.core.tree.fork + 1
|
|
403
|
-
await this.core.truncate(newLength, fork, this.
|
|
666
|
+
await this.core.truncate(newLength, fork, this.auth)
|
|
404
667
|
|
|
405
668
|
// TODO: Should propagate from an event triggered by the oplog
|
|
406
669
|
this.replicator.updateAll()
|
|
@@ -408,42 +671,108 @@ module.exports = class Hypercore extends EventEmitter {
|
|
|
408
671
|
|
|
409
672
|
async append (blocks) {
|
|
410
673
|
if (this.opened === false) await this.opening
|
|
411
|
-
if (this.writable === false) throw
|
|
674
|
+
if (this.writable === false) throw SESSION_NOT_WRITABLE()
|
|
412
675
|
|
|
413
|
-
|
|
414
|
-
const buffers = new Array(blks.length)
|
|
676
|
+
blocks = Array.isArray(blocks) ? blocks : [blocks]
|
|
415
677
|
|
|
416
|
-
|
|
417
|
-
const blk = blks[i]
|
|
678
|
+
const preappend = this.encryption && this._preappend
|
|
418
679
|
|
|
419
|
-
|
|
420
|
-
? blk
|
|
421
|
-
: this.valueEncoding
|
|
422
|
-
? c.encode(this.valueEncoding, blk)
|
|
423
|
-
: Buffer.from(blk)
|
|
680
|
+
const buffers = this.encodeBatch !== null ? this.encodeBatch(blocks) : new Array(blocks.length)
|
|
424
681
|
|
|
425
|
-
|
|
682
|
+
if (this.encodeBatch === null) {
|
|
683
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
684
|
+
buffers[i] = this._encode(this.valueEncoding, blocks[i])
|
|
685
|
+
}
|
|
426
686
|
}
|
|
427
687
|
|
|
428
|
-
return await this.core.append(buffers, this.
|
|
688
|
+
return await this.core.append(buffers, this.auth, { preappend })
|
|
429
689
|
}
|
|
430
690
|
|
|
431
|
-
|
|
432
|
-
|
|
691
|
+
async treeHash (length) {
|
|
692
|
+
if (length === undefined) {
|
|
693
|
+
await this.ready()
|
|
694
|
+
length = this.core.length
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const roots = await this.core.tree.getRoots(length)
|
|
698
|
+
return this.crypto.tree(roots)
|
|
433
699
|
}
|
|
434
700
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
701
|
+
registerExtension (name, handlers = {}) {
|
|
702
|
+
if (this.extensions.has(name)) {
|
|
703
|
+
const ext = this.extensions.get(name)
|
|
704
|
+
ext.handlers = handlers
|
|
705
|
+
ext.encoding = c.from(codecs(handlers.encoding) || c.buffer)
|
|
706
|
+
ext.session = this
|
|
707
|
+
return ext
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const ext = {
|
|
711
|
+
name,
|
|
712
|
+
handlers,
|
|
713
|
+
encoding: c.from(codecs(handlers.encoding) || c.buffer),
|
|
714
|
+
session: this,
|
|
715
|
+
send (message, peer) {
|
|
716
|
+
const buffer = c.encode(this.encoding, message)
|
|
717
|
+
peer.extension(name, buffer)
|
|
718
|
+
},
|
|
719
|
+
broadcast (message) {
|
|
720
|
+
const buffer = c.encode(this.encoding, message)
|
|
721
|
+
for (const peer of this.session.peers) {
|
|
722
|
+
peer.extension(name, buffer)
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
destroy () {
|
|
726
|
+
for (const peer of this.session.peers) {
|
|
727
|
+
if (peer.extensions.get(name) === ext) peer.extensions.delete(name)
|
|
728
|
+
}
|
|
729
|
+
this.session.extensions.delete(name)
|
|
730
|
+
},
|
|
731
|
+
_onmessage (state, peer) {
|
|
732
|
+
const m = this.encoding.decode(state)
|
|
733
|
+
if (this.handlers.onmessage) this.handlers.onmessage(m, peer)
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
this.extensions.set(name, ext)
|
|
738
|
+
for (const peer of this.peers) {
|
|
739
|
+
peer.extensions.set(name, ext)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return ext
|
|
438
743
|
}
|
|
439
|
-
}
|
|
440
744
|
|
|
441
|
-
|
|
745
|
+
_encode (enc, val) {
|
|
746
|
+
const state = { start: this.padding, end: this.padding, buffer: null }
|
|
442
747
|
|
|
443
|
-
|
|
444
|
-
|
|
748
|
+
if (b4a.isBuffer(val)) {
|
|
749
|
+
if (state.start === 0) return val
|
|
750
|
+
state.end += val.byteLength
|
|
751
|
+
} else if (enc) {
|
|
752
|
+
enc.preencode(state, val)
|
|
753
|
+
} else {
|
|
754
|
+
val = b4a.from(val)
|
|
755
|
+
if (state.start === 0) return val
|
|
756
|
+
state.end += val.byteLength
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
state.buffer = b4a.allocUnsafe(state.end)
|
|
760
|
+
|
|
761
|
+
if (enc) enc.encode(state, val)
|
|
762
|
+
else state.buffer.set(val, state.start)
|
|
763
|
+
|
|
764
|
+
return state.buffer
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
_decode (enc, block) {
|
|
768
|
+
block = block.subarray(this.padding)
|
|
769
|
+
if (enc) return c.decode(enc, block)
|
|
770
|
+
return block
|
|
771
|
+
}
|
|
445
772
|
}
|
|
446
773
|
|
|
774
|
+
function noop () {}
|
|
775
|
+
|
|
447
776
|
function isStream (s) {
|
|
448
777
|
return typeof s === 'object' && s && typeof s.pipe === 'function'
|
|
449
778
|
}
|
|
@@ -457,5 +786,14 @@ function requireMaybe (name) {
|
|
|
457
786
|
}
|
|
458
787
|
|
|
459
788
|
function toHex (buf) {
|
|
460
|
-
return buf &&
|
|
789
|
+
return buf && b4a.toString(buf, 'hex')
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function preappend (blocks) {
|
|
793
|
+
const offset = this.core.tree.length
|
|
794
|
+
const fork = this.core.tree.fork
|
|
795
|
+
|
|
796
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
797
|
+
this.encryption.encrypt(offset + i, blocks[i], fork)
|
|
798
|
+
}
|
|
461
799
|
}
|