@cero-base/core 1.3.0 → 1.5.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/package.json +32 -15
- package/src/database/bootstrap.js +62 -8
- package/src/database/changes.js +120 -0
- package/src/database/dispatch.js +6 -6
- package/src/database/envelope.js +32 -0
- package/src/database/index.js +109 -7
- package/src/lib/spec/index.js +35 -2
- package/src/lib/spec/schema.json +23 -1
- package/src/network/index.js +63 -53
- package/src/network/{bluetooth.js → transports/ble.js} +152 -167
- package/src/network/transports/dht.js +110 -0
- package/src/network/{gatt-stream.js → transports/gatt.js} +4 -6
- package/src/rpc/index.js +23 -0
- package/types/database/changes.d.ts +15 -0
- package/types/database/envelope.d.ts +19 -0
- package/types/database/index.d.ts +32 -0
- package/types/lib/spec/index.d.ts +25 -12
- package/types/network/index.d.ts +21 -4
- package/types/network/{bluetooth.d.ts → transports/ble.d.ts} +25 -51
- package/types/network/transports/dht.d.ts +75 -0
- package/types/network/{gatt-stream.d.ts → transports/gatt.d.ts} +2 -3
- package/types/rpc/index.d.ts +10 -0
package/src/network/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import Hyperswarm from 'hyperswarm'
|
|
2
1
|
import NoiseSecretStream from '@hyperswarm/secret-stream'
|
|
3
2
|
import BlindPairing from 'blind-pairing'
|
|
3
|
+
import BlindPeering from 'blind-peering'
|
|
4
4
|
import ProtomuxWakeup from 'protomux-wakeup'
|
|
5
5
|
import ReadyResource from 'ready-resource'
|
|
6
6
|
import safetyCatch from 'safety-catch'
|
|
7
|
+
import { decode as decodeKey } from 'hypercore-id-encoding'
|
|
7
8
|
import b4a from 'b4a'
|
|
8
|
-
import { hash } from 'hypercore-crypto'
|
|
9
9
|
|
|
10
10
|
import { ACTIVE, PASSIVE } from '../lib/constants.js'
|
|
11
11
|
import { CeroError } from '../lib/errors.js'
|
|
12
12
|
import { Discovery } from './discovery.js'
|
|
13
|
+
import { DHTTransport, channelTopic } from './transports/dht.js'
|
|
14
|
+
|
|
15
|
+
export { channelTopic }
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* @typedef {object} NetworkOpts
|
|
@@ -18,6 +21,8 @@ import { Discovery } from './discovery.js'
|
|
|
18
21
|
* @property {(remotePublicKey: Uint8Array, payload: any) => boolean} [firewall] Incoming-connection filter.
|
|
19
22
|
* @property {Uint8Array[]} [relayThrough] Relay public keys to tunnel through.
|
|
20
23
|
* @property {string} [channel] Optional network-isolation label; only same-channel peers meet.
|
|
24
|
+
* @property {any} [store] Corestore; required for mirrors (blind peers replicate its cores).
|
|
25
|
+
* @property {Array<string | Uint8Array>} [mirrors] Blind-peer public keys; each attached room/blob core is mirrored through them for offline sync.
|
|
21
26
|
*
|
|
22
27
|
* @typedef {{ replicate: (stream: any) => any }} Replicable
|
|
23
28
|
*/
|
|
@@ -28,21 +33,29 @@ import { Discovery } from './discovery.js'
|
|
|
28
33
|
*/
|
|
29
34
|
export class Network extends ReadyResource {
|
|
30
35
|
/** @param {NetworkOpts} [opts] */
|
|
31
|
-
constructor({ identity, bootstrap, firewall, relayThrough, channel } = {}) {
|
|
36
|
+
constructor({ identity, bootstrap, firewall, relayThrough, channel, store, mirrors } = {}) {
|
|
32
37
|
super()
|
|
33
38
|
this.identity = identity || null
|
|
34
39
|
this.bootstrap = bootstrap || null
|
|
35
40
|
this.firewall = firewall || null
|
|
36
41
|
this.relayThrough = relayThrough || null
|
|
37
42
|
this.channel = channel || null
|
|
43
|
+
this.store = store || null
|
|
44
|
+
this.mirrors = (mirrors || []).map((k) => (typeof k === 'string' ? decodeKey(k) : k))
|
|
38
45
|
|
|
39
|
-
this.
|
|
46
|
+
this._dht = null
|
|
40
47
|
this.wakeup = new ProtomuxWakeup()
|
|
41
48
|
|
|
42
49
|
this._replicateables = new Set()
|
|
43
50
|
this._discoveries = new Set()
|
|
44
51
|
this._injected = new Set()
|
|
45
52
|
this._blind = null
|
|
53
|
+
this._blindPeering = null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @returns {any} The underlying hyperswarm, or null before ready / after close. */
|
|
57
|
+
get swarm() {
|
|
58
|
+
return this._dht ? this._dht.swarm : null
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
/**
|
|
@@ -143,24 +156,18 @@ export class Network extends ReadyResource {
|
|
|
143
156
|
}
|
|
144
157
|
|
|
145
158
|
async _open() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.swarm = new Hyperswarm(opts)
|
|
154
|
-
|
|
155
|
-
if (this.channel) {
|
|
156
|
-
const channel = this.channel
|
|
157
|
-
const join = this.swarm.join.bind(this.swarm)
|
|
158
|
-
const leave = this.swarm.leave.bind(this.swarm)
|
|
159
|
-
this.swarm.join = (topic, opts) => join(channelTopic(topic, channel), opts)
|
|
160
|
-
this.swarm.leave = (topic) => leave(channelTopic(topic, channel))
|
|
161
|
-
}
|
|
159
|
+
this._dht = new DHTTransport({
|
|
160
|
+
identity: this.identity,
|
|
161
|
+
bootstrap: this.bootstrap,
|
|
162
|
+
firewall: this.firewall,
|
|
163
|
+
relayThrough: this.relayThrough,
|
|
164
|
+
channel: this.channel
|
|
165
|
+
})
|
|
162
166
|
|
|
163
|
-
|
|
167
|
+
// DHTTransport owns the swarm's lifecycle; Network subscribes to its events
|
|
168
|
+
// because the handlers touch Network state (wakeup, replicateables, emit).
|
|
169
|
+
const swarm = this._dht.swarm
|
|
170
|
+
swarm.on('connection', (stream, info) => {
|
|
164
171
|
if (this.closing || this.closed) {
|
|
165
172
|
stream.destroy()
|
|
166
173
|
return
|
|
@@ -169,8 +176,16 @@ export class Network extends ReadyResource {
|
|
|
169
176
|
for (const r of this._replicateables) replicateInto(r, stream)
|
|
170
177
|
this.emit('connection', stream, info)
|
|
171
178
|
})
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
swarm.on('peer-add', (peer) => this.emit('peer-add', peer))
|
|
180
|
+
swarm.on('peer-remove', (peer) => this.emit('peer-remove', peer))
|
|
181
|
+
|
|
182
|
+
if (this.store && this.mirrors.length) {
|
|
183
|
+
this._blindPeering = new BlindPeering(swarm.dht, this.store, {
|
|
184
|
+
keys: this.mirrors,
|
|
185
|
+
wakeup: this.wakeup,
|
|
186
|
+
pick: 2
|
|
187
|
+
})
|
|
188
|
+
}
|
|
174
189
|
}
|
|
175
190
|
|
|
176
191
|
/**
|
|
@@ -179,9 +194,8 @@ export class Network extends ReadyResource {
|
|
|
179
194
|
* @param {{ timeout?: number }} [opts]
|
|
180
195
|
* @returns {Promise<void>}
|
|
181
196
|
*/
|
|
182
|
-
async flush(
|
|
183
|
-
|
|
184
|
-
await Promise.race([this.swarm.flush(), new Promise((r) => setTimeout(r, timeout))])
|
|
197
|
+
async flush(opts) {
|
|
198
|
+
await this._dht?.flush(opts)
|
|
185
199
|
}
|
|
186
200
|
|
|
187
201
|
/**
|
|
@@ -190,13 +204,9 @@ export class Network extends ReadyResource {
|
|
|
190
204
|
* @returns {Promise<void>}
|
|
191
205
|
*/
|
|
192
206
|
async suspend() {
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
await this.swarm.suspend()
|
|
197
|
-
} catch (err) {
|
|
198
|
-
safetyCatch(err)
|
|
199
|
-
}
|
|
207
|
+
if (this.closing || this.closed) return
|
|
208
|
+
await this._blindPeering?.suspend()
|
|
209
|
+
await this._dht?.suspend()
|
|
200
210
|
}
|
|
201
211
|
|
|
202
212
|
/**
|
|
@@ -205,13 +215,9 @@ export class Network extends ReadyResource {
|
|
|
205
215
|
* @returns {Promise<void>}
|
|
206
216
|
*/
|
|
207
217
|
async resume() {
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
await this.swarm.resume()
|
|
212
|
-
} catch (err) {
|
|
213
|
-
safetyCatch(err)
|
|
214
|
-
}
|
|
218
|
+
if (this.closing || this.closed) return
|
|
219
|
+
await this._dht?.resume()
|
|
220
|
+
await this._blindPeering?.resume()
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
async _close() {
|
|
@@ -232,27 +238,31 @@ export class Network extends ReadyResource {
|
|
|
232
238
|
}
|
|
233
239
|
}
|
|
234
240
|
|
|
235
|
-
if (this.
|
|
241
|
+
if (this._blindPeering) {
|
|
236
242
|
try {
|
|
237
|
-
await
|
|
243
|
+
await this._blindPeering.close()
|
|
238
244
|
} catch (err) {
|
|
239
245
|
safetyCatch(err)
|
|
240
246
|
}
|
|
241
|
-
this.
|
|
247
|
+
this._blindPeering = null
|
|
242
248
|
}
|
|
243
249
|
|
|
244
|
-
if (this.
|
|
250
|
+
if (this._blind) {
|
|
245
251
|
try {
|
|
246
|
-
await this.
|
|
252
|
+
await (await this._blind).close()
|
|
247
253
|
} catch (err) {
|
|
248
254
|
safetyCatch(err)
|
|
249
255
|
}
|
|
256
|
+
this._blind = null
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (this._dht) {
|
|
250
260
|
try {
|
|
251
|
-
await this.
|
|
261
|
+
await this._dht.destroy()
|
|
252
262
|
} catch (err) {
|
|
253
263
|
safetyCatch(err)
|
|
254
264
|
}
|
|
255
|
-
this.
|
|
265
|
+
this._dht = null
|
|
256
266
|
}
|
|
257
267
|
|
|
258
268
|
if (this.wakeup) {
|
|
@@ -300,6 +310,12 @@ export class Network extends ReadyResource {
|
|
|
300
310
|
if (!core) throw CeroError.REQUIRED('core')
|
|
301
311
|
this._replicateables.add(core)
|
|
302
312
|
for (const stream of this.connections) replicateInto(core, stream)
|
|
313
|
+
// mirror the core so it stays available when its writers are offline —
|
|
314
|
+
// autobees announce their whole writer set, plain cores (blobs) just themselves
|
|
315
|
+
if (this._blindPeering) {
|
|
316
|
+
if (core.wakeupCapability) this._blindPeering.addAutobaseBackground(core)
|
|
317
|
+
else this._blindPeering.addCoreBackground(core)
|
|
318
|
+
}
|
|
303
319
|
}
|
|
304
320
|
|
|
305
321
|
/**
|
|
@@ -350,12 +366,6 @@ function replicateInto(core, stream) {
|
|
|
350
366
|
}
|
|
351
367
|
}
|
|
352
368
|
|
|
353
|
-
// A channel re-namespaces every swarm topic so only same-channel peers meet.
|
|
354
|
-
// No channel → identity (unchanged, back-compat).
|
|
355
|
-
export function channelTopic(topic, channel) {
|
|
356
|
-
return channel ? hash([topic, b4a.from(channel)]) : topic
|
|
357
|
-
}
|
|
358
|
-
|
|
359
369
|
function isTopic(x) {
|
|
360
370
|
return b4a.isBuffer(x) && x.length === 32
|
|
361
371
|
}
|