@cero-base/cero 1.18.2 → 2.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 +38 -644
- package/package.json +16 -12
- package/src/build/index.js +21 -50
- package/src/build/internal.js +121 -0
- package/src/build/schemas.js +2 -8
- package/src/extensions/handle-sync.js +3 -10
- package/src/extensions/index.js +6 -0
- package/src/extensions/profile-sync.js +0 -5
- package/src/handle/index.js +255 -300
- package/src/index.js +65 -112
- package/src/lib/bluetooth.js +25 -56
- package/src/lib/constants.js +0 -15
- package/src/lib/operators.js +24 -74
- package/src/lib/peek.js +4 -8
- package/src/lib/refs.js +46 -0
- package/src/lib/spec.js +2 -3
- package/src/local/index.js +4 -5
- package/src/rpc/client.js +27 -39
- package/src/rpc/index.js +3 -3
- package/src/rpc/server.js +29 -40
- package/types/build/index.d.ts +19 -7
- package/types/build/internal.d.ts +78 -0
- package/types/build/schemas.d.ts +3 -3
- package/types/extensions/handle-sync.d.ts +4 -9
- package/types/extensions/index.d.ts +24 -2
- package/types/extensions/profile-sync.d.ts +2 -6
- package/types/handle/index.d.ts +218 -254
- package/types/index.d.ts +78 -102
- package/types/lib/bluetooth.d.ts +24 -46
- package/types/lib/constants.d.ts +5 -16
- package/types/lib/operators.d.ts +49 -77
- package/types/lib/peek.d.ts +3 -4
- package/types/lib/refs.d.ts +36 -0
- package/types/lib/spec.d.ts +5 -1
- package/types/local/index.d.ts +24 -23
- package/types/rpc/client.d.ts +107 -127
- package/types/rpc/index.d.ts +7 -2
- package/types/rpc/server.d.ts +62 -76
- package/src/build/builtins.js +0 -174
- package/src/lib/internal.js +0 -9
- package/src/lib/utils.js +0 -67
- package/types/build/builtins.d.ts +0 -100
- package/types/lib/internal.d.ts +0 -24
- package/types/lib/utils.d.ts +0 -55
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import Hypercore from 'hypercore'
|
|
|
2
2
|
import HypercoreStorage from 'hypercore-storage'
|
|
3
3
|
import Corestore from 'corestore'
|
|
4
4
|
import safetyCatch from 'safety-catch'
|
|
5
|
+
import c from 'compact-encoding'
|
|
5
6
|
import fs from 'fs'
|
|
6
7
|
|
|
7
8
|
import { Identity } from '@cero-base/core/identity'
|
|
@@ -29,8 +30,8 @@ import {
|
|
|
29
30
|
} from './lib/operators.js'
|
|
30
31
|
import { peek } from './lib/peek.js'
|
|
31
32
|
import { t, schema } from './lib/spec.js'
|
|
32
|
-
import { FLUSH } from './lib/constants.js'
|
|
33
|
-
import {
|
|
33
|
+
import { FLUSH, TIMEOUT } from './lib/constants.js'
|
|
34
|
+
import { registry } from './extensions/index.js'
|
|
34
35
|
|
|
35
36
|
export { Handle, Ref, Local }
|
|
36
37
|
export {
|
|
@@ -68,21 +69,18 @@ export { t, schema } from './lib/spec.js'
|
|
|
68
69
|
* @property {number[]} [backoffs] Swarm reconnect backoff tiers in ms (testing/tuning).
|
|
69
70
|
* @property {string} [channel] Optional network-isolation label; only same-channel peers connect.
|
|
70
71
|
* @property {Array<string | Uint8Array>} [mirrors] Blind-peer public keys. Rooms and files are mirrored through them so peers sync even when never online at the same time. Mirrors hold only encrypted blocks — they never read your data.
|
|
71
|
-
* @property {Uint8Array} [key]
|
|
72
|
+
* @property {Uint8Array} [key] Existing database key to recover into, skipping the pointer lookup.
|
|
72
73
|
* @property {Uint8Array} [encryptionKey] Pre-existing encryption key.
|
|
73
74
|
* @property {Record<string, Function>} [routes] Custom RPC routes for the database dispatcher.
|
|
74
75
|
* @property {(err: any) => void} [onerror] Background-task error handler.
|
|
75
|
-
* @property {
|
|
76
|
-
* @property {number} [recoveryTimeout] Max wait for peer data + writer capability during recovery.
|
|
76
|
+
* @property {number} [recoveryTimeout] Max wait to find another device and be admitted, in ms. Defaults to 30000.
|
|
77
77
|
* @property {Uint8Array} [storageKey] 32-byte key encrypting local key material (master seed, device keypairs) at rest. Source it from the OS keychain — cero never stores it.
|
|
78
78
|
* @property {boolean} [extensions] `false` disables the bundled extensions (profileSync, handleSync) for this instance. Build with `{ extensions: false }` too so the spec matches.
|
|
79
79
|
* @property {boolean | { autoStart?: boolean, backend?: any, maxOutbound?: number, maxInbound?: number, pipe?: 'l2cap' | 'gatt' }} [bluetooth] `true` enables nearby (Bluetooth) sync via `me.bluetooth` (auto-started). `{ autoStart: false }` creates the facade without starting the radio — the app calls `me.bluetooth.start()`/`stop()` (user toggle). `backend` injects a bare-bluetooth-shaped backend (tests). `maxOutbound`/`maxInbound` cap concurrent outbound links and inbound sessions. `pipe` picks the data pipe — `'l2cap'` (default, faster) or `'gatt'`; both peers must match. Absent backend on an unsupported host → `me.bluetooth.state === 'unsupported'`.
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Open (or create) a cero handle at `dir`.
|
|
84
|
-
* identity, then returns a ready root `Handle` with all schema refs
|
|
85
|
-
* attached as properties.
|
|
83
|
+
* Open (or create) a cero handle at `dir`.
|
|
86
84
|
*
|
|
87
85
|
* @param {string} dir Data directory.
|
|
88
86
|
* @param {any} spec Built spec — output of `cero/build`.
|
|
@@ -99,9 +97,7 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
99
97
|
let network = null
|
|
100
98
|
let discovery = null
|
|
101
99
|
let me = null
|
|
102
|
-
//
|
|
103
|
-
// timeout, extension setup) must close whatever opened — else the corestore
|
|
104
|
-
// lock leaks and a retry in the same process hits locked storage.
|
|
100
|
+
// any failure during open must close what opened, or the storage lock leaks
|
|
105
101
|
try {
|
|
106
102
|
await storage.ready()
|
|
107
103
|
await fs.promises.chmod(`${dir}/main`, 0o700)
|
|
@@ -115,10 +111,11 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
115
111
|
|
|
116
112
|
const { identity, fresh } = await resolveIdentity(opts, local)
|
|
117
113
|
const writer = local ? (await local.store.get('keypair')).data : null
|
|
114
|
+
// a supplied identity on a device with no writer recovers; only a cero-minted identity creates
|
|
115
|
+
const recovering = !writer && (!!opts.key || !fresh)
|
|
116
|
+
const timeout = opts.recoveryTimeout || TIMEOUT
|
|
118
117
|
|
|
119
|
-
//
|
|
120
|
-
// including no channel at all, which would silently rejoin the global network — is a
|
|
121
|
-
// misconfiguration that could leak data across networks, so reject it.
|
|
118
|
+
// a storage remembers its channel; reopening under another would silently rejoin the global network
|
|
122
119
|
if (local) {
|
|
123
120
|
const stored = (await local.store.get('environment')).data?.channel ?? null
|
|
124
121
|
const wanted = opts.channel ?? null
|
|
@@ -136,10 +133,21 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
136
133
|
})
|
|
137
134
|
await network.ready()
|
|
138
135
|
discovery = network.join(identity.topic)
|
|
139
|
-
// a
|
|
140
|
-
// before proceeding only delays first onboarding
|
|
136
|
+
// a fresh identity has no peers yet, flushing the announce would only delay onboarding
|
|
141
137
|
if (!fresh) await Promise.race([discovery.flush(), new Promise((r) => setTimeout(r, FLUSH))])
|
|
142
138
|
|
|
139
|
+
// the pointer core: identity-signed, written once by the creating device, holds the root key
|
|
140
|
+
const manifest = pointerManifest(store, identity)
|
|
141
|
+
const pointer = store.get(
|
|
142
|
+
!writer && !recovering
|
|
143
|
+
? { keyPair: { publicKey: identity.publicKey, secretKey: identity.secretKey }, manifest }
|
|
144
|
+
: { key: Hypercore.key(manifest) }
|
|
145
|
+
)
|
|
146
|
+
await pointer.ready()
|
|
147
|
+
network.attach(pointer)
|
|
148
|
+
|
|
149
|
+
const key = opts.key || (recovering ? await readPointer(pointer, timeout) : undefined)
|
|
150
|
+
|
|
143
151
|
me = new Handle({
|
|
144
152
|
storage,
|
|
145
153
|
store,
|
|
@@ -151,57 +159,31 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
151
159
|
opts,
|
|
152
160
|
dir,
|
|
153
161
|
routes: opts.routes,
|
|
154
|
-
key
|
|
162
|
+
key,
|
|
155
163
|
encryptionKey: opts.encryptionKey,
|
|
156
164
|
keyPair: writer ? { publicKey: writer.publicKey, secretKey: writer.secretKey } : undefined,
|
|
157
165
|
pair: false
|
|
158
166
|
})
|
|
159
167
|
await me.ready()
|
|
168
|
+
me.once('close', () => {
|
|
169
|
+
network.detach(pointer)
|
|
170
|
+
pointer.close().catch(safetyCatch)
|
|
171
|
+
})
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
// authoring genesis twice forks the seed-derived writer core (a writable
|
|
163
|
-
// core has one author, ever). Ask the peers directly with a plain session
|
|
164
|
-
// on the genesis core: the base never replicates an empty local core.
|
|
165
|
-
// Offline reuse is undetectable — this catches the reachable-peer case.
|
|
166
|
-
if (!writer && !opts.key && !opts.recovery && !fresh) {
|
|
167
|
-
const genesis = store.get({
|
|
168
|
-
key: Hypercore.key({
|
|
169
|
-
version: store.manifestVersion,
|
|
170
|
-
signers: [{ publicKey: identity.publicKey }]
|
|
171
|
-
})
|
|
172
|
-
})
|
|
173
|
-
await genesis.ready()
|
|
174
|
-
network.attach(genesis)
|
|
175
|
-
try {
|
|
176
|
-
const until = Date.now() + 1500
|
|
177
|
-
while (Date.now() < until) {
|
|
178
|
-
if (genesis.length > 0 || genesis.peers.some((p) => p.remoteLength > 0)) {
|
|
179
|
-
throw CeroError.CONFLICT('identity already has history — open with { recovery: true }')
|
|
180
|
-
}
|
|
181
|
-
await new Promise((r) => setTimeout(r, 100))
|
|
182
|
-
}
|
|
183
|
-
} finally {
|
|
184
|
-
network.detach(genesis)
|
|
185
|
-
await genesis.close()
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
// decided by the stored writer and the caller's intent, never by the local
|
|
189
|
-
// core's length: a same-identity device opens on the genesis core, which
|
|
190
|
-
// fills with the first device's blocks as soon as a peer connects
|
|
191
|
-
if (!writer && (!opts.key || opts.recovery)) {
|
|
173
|
+
if (!writer) {
|
|
192
174
|
const result = await me.bootstrap({
|
|
193
175
|
name: opts.name || null,
|
|
194
176
|
isMobile: opts.isMobile === true,
|
|
195
|
-
recovering
|
|
196
|
-
|
|
177
|
+
recovering,
|
|
178
|
+
timeout
|
|
197
179
|
})
|
|
198
|
-
if (local
|
|
180
|
+
if (local) {
|
|
199
181
|
await local.store.set('keypair', {
|
|
200
182
|
publicKey: result.writer.publicKey,
|
|
201
183
|
secretKey: result.writer.secretKey
|
|
202
184
|
})
|
|
203
185
|
}
|
|
204
|
-
if (!
|
|
186
|
+
if (!recovering) {
|
|
205
187
|
const ts = Date.now()
|
|
206
188
|
await me.store.call('add-member', {
|
|
207
189
|
id: identity.id,
|
|
@@ -211,29 +193,20 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
211
193
|
createdAt: ts,
|
|
212
194
|
updatedAt: ts
|
|
213
195
|
})
|
|
196
|
+
if (pointer.length === 0) await pointer.append(c.encode(c.fixed32, me.store.key))
|
|
214
197
|
}
|
|
215
198
|
}
|
|
216
|
-
if (opts.recovery) await me.recover({ timeout: opts.recoveryTimeout })
|
|
217
199
|
|
|
218
|
-
for (const ext of
|
|
200
|
+
for (const ext of registry) {
|
|
219
201
|
if (ext.bundled && opts.extensions === false) continue
|
|
220
202
|
const off = await ext.setup?.(me)
|
|
221
203
|
if (typeof off === 'function') me.once('close', off)
|
|
222
204
|
}
|
|
223
205
|
|
|
224
206
|
if (opts.bluetooth) {
|
|
225
|
-
|
|
226
|
-
// object (pre-1.3 shape, has Central/Server) is still accepted.
|
|
227
|
-
const bt =
|
|
228
|
-
opts.bluetooth === true
|
|
229
|
-
? {}
|
|
230
|
-
: opts.bluetooth.Central
|
|
231
|
-
? { backend: opts.bluetooth }
|
|
232
|
-
: opts.bluetooth
|
|
207
|
+
const bt = opts.bluetooth === true ? {} : opts.bluetooth
|
|
233
208
|
me.bluetooth = new Bluetooth(me, {
|
|
234
|
-
//
|
|
235
|
-
// backend (= lazy-load bare-bluetooth) into an explicit null
|
|
236
|
-
// (= disabled), leaving BLE 'unsupported' on every platform
|
|
209
|
+
// an omitted backend lazy-loads bare-bluetooth, null disables it
|
|
237
210
|
backend: bt.backend,
|
|
238
211
|
autoStart: bt.autoStart !== false,
|
|
239
212
|
maxOutbound: bt.maxOutbound,
|
|
@@ -247,8 +220,7 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
247
220
|
bind(me, null)
|
|
248
221
|
return me
|
|
249
222
|
} catch (err) {
|
|
250
|
-
//
|
|
251
|
-
// tear the raw resources down in reverse order.
|
|
223
|
+
// before the root Handle exists, tear the raw resources down in reverse order
|
|
252
224
|
if (me) await me.close().catch(safetyCatch)
|
|
253
225
|
else {
|
|
254
226
|
await discovery?.destroy().catch(safetyCatch)
|
|
@@ -262,9 +234,7 @@ export async function cero(dir, spec, opts = {}) {
|
|
|
262
234
|
}
|
|
263
235
|
|
|
264
236
|
/**
|
|
265
|
-
* Restore a cero instance from a mnemonic phrase.
|
|
266
|
-
* instance, wipes the on-disk `main/` tree and re-opens with `recovery: true`
|
|
267
|
-
* so the writer slot is re-claimed.
|
|
237
|
+
* Restore a cero instance from a mnemonic phrase.
|
|
268
238
|
*
|
|
269
239
|
* @param {Handle} me Existing root handle to restore.
|
|
270
240
|
* @param {string} phrase BIP-39 mnemonic phrase.
|
|
@@ -274,48 +244,23 @@ export async function restore(me, phrase) {
|
|
|
274
244
|
if (!me?._dir) throw CeroError.INVALID('me must be a cero instance')
|
|
275
245
|
if (!phrase || typeof phrase !== 'string') throw CeroError.INVALID('phrase must be a string')
|
|
276
246
|
|
|
277
|
-
// Already this identity? Nothing to restore — return the running instance.
|
|
278
247
|
const current = await Identity.fromSeed(Identity.toSeed(phrase))
|
|
279
248
|
if (current.id === me.identity.id) return me
|
|
280
249
|
|
|
281
|
-
|
|
282
|
-
// channel must carry over — without it the recovered instance rejoins the
|
|
283
|
-
// global identity topic and never meets its channeled peers (recovery timeout).
|
|
250
|
+
// everything carries over except the identity, the channel above all
|
|
284
251
|
const {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
routes,
|
|
290
|
-
recoveryTimeout,
|
|
291
|
-
channel,
|
|
292
|
-
mirrors,
|
|
293
|
-
storageKey,
|
|
294
|
-
extensions
|
|
295
|
-
} = opts
|
|
252
|
+
_dir: dir,
|
|
253
|
+
spec,
|
|
254
|
+
_opts: { seed, identity, key, keyPair, ...opts }
|
|
255
|
+
} = me
|
|
296
256
|
|
|
297
257
|
await me.close()
|
|
298
258
|
await fs.promises.rm(`${dir}/main`, { recursive: true, force: true })
|
|
299
259
|
|
|
300
|
-
return cero(dir, spec, {
|
|
301
|
-
name,
|
|
302
|
-
bootstrap,
|
|
303
|
-
isMobile,
|
|
304
|
-
onerror,
|
|
305
|
-
routes,
|
|
306
|
-
recoveryTimeout,
|
|
307
|
-
channel,
|
|
308
|
-
mirrors,
|
|
309
|
-
storageKey,
|
|
310
|
-
extensions,
|
|
311
|
-
phrase,
|
|
312
|
-
recovery: true
|
|
313
|
-
})
|
|
260
|
+
return cero(dir, spec, { ...opts, phrase })
|
|
314
261
|
}
|
|
315
262
|
|
|
316
|
-
//
|
|
317
|
-
// `import { define }` and `cero.define(...)` work. Explicit assignments (not
|
|
318
|
-
// `Object.assign`) so tsc reflects them onto the `cero` namespace in the .d.ts.
|
|
263
|
+
// the facade: cero.put and import { put } are the same function
|
|
319
264
|
cero.t = t
|
|
320
265
|
cero.put = put
|
|
321
266
|
cero.set = set
|
|
@@ -334,18 +279,26 @@ cero.restore = restore
|
|
|
334
279
|
cero.schema = schema
|
|
335
280
|
cero.bind = bind
|
|
336
281
|
cero.define = define
|
|
337
|
-
// test-only
|
|
338
|
-
cero.
|
|
339
|
-
//
|
|
340
|
-
// Accepts both `use(a, b)` and `use([a, b])` (and a mix) for easier composition.
|
|
341
|
-
// A named extension replaces any registered one with the same name — so
|
|
342
|
-
// `use(profileSync({ fields }))` reconfigures the bundled default instead of
|
|
343
|
-
// doubling it.
|
|
282
|
+
// test-only
|
|
283
|
+
cero._registry = registry
|
|
284
|
+
// a bare function is shorthand for { setup }; a named extension replaces one of the same name
|
|
344
285
|
cero.use = (...exts) => {
|
|
345
286
|
for (const e of exts.flat().map((e) => (typeof e === 'function' ? { setup: e } : e))) {
|
|
346
|
-
const i = e.name ?
|
|
347
|
-
if (i >= 0)
|
|
348
|
-
else
|
|
287
|
+
const i = e.name ? registry.findIndex((x) => x.name === e.name) : -1
|
|
288
|
+
if (i >= 0) registry[i] = e
|
|
289
|
+
else registry.push(e)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function pointerManifest(store, identity) {
|
|
294
|
+
return { version: store.manifestVersion, signers: [{ publicKey: identity.publicKey }] }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function readPointer(pointer, timeout) {
|
|
298
|
+
try {
|
|
299
|
+
return c.decode(c.fixed32, await pointer.get(0, { timeout }))
|
|
300
|
+
} catch {
|
|
301
|
+
throw CeroError.TIMED_OUT('recovery: finding a device of this identity')
|
|
349
302
|
}
|
|
350
303
|
}
|
|
351
304
|
|
package/src/lib/bluetooth.js
CHANGED
|
@@ -8,19 +8,8 @@ import { Pairing } from '@cero-base/core/pairing'
|
|
|
8
8
|
import { Invite } from '@cero-base/core/invite'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* `me.bluetooth` — the app-facing surface for nearby (Bluetooth) sync, a thin
|
|
12
|
-
*
|
|
13
|
-
* bytes; capability-gated replication still decides what syncs. Discovery is
|
|
14
|
-
* one topic-derived service UUID at a time (tag `cero-ble`) — the data service
|
|
15
|
-
* sits on a fixed per-tag UUID, so switching topics only retunes the radio.
|
|
16
|
-
*
|
|
17
|
-
* ```js
|
|
18
|
-
* const me = await cero(dir, spec, { channel, bluetooth: true })
|
|
19
|
-
* me.bluetooth.state // 'unsupported' | 'unauthorized' | 'off' | 'waiting' | 'starting' | 'on'
|
|
20
|
-
* await me.bluetooth.start()
|
|
21
|
-
* me.bluetooth.peers // Map of live BLE links
|
|
22
|
-
* me.bluetooth.on('update', () => {})
|
|
23
|
-
* ```
|
|
11
|
+
* `me.bluetooth` — the app-facing surface for nearby (Bluetooth) sync, a thin facade over
|
|
12
|
+
* ble-swarm.
|
|
24
13
|
*
|
|
25
14
|
* @extends ReadyResource
|
|
26
15
|
*/
|
|
@@ -43,14 +32,11 @@ export class Bluetooth extends ReadyResource {
|
|
|
43
32
|
this._restorePending = false
|
|
44
33
|
|
|
45
34
|
const identity = handle.identity
|
|
46
|
-
//
|
|
47
|
-
// channelless — any nearby cero device links (global nearby; strangers
|
|
48
|
-
// still sync zero bytes, replication is capability-gated)
|
|
35
|
+
// one mesh topic per channel, or a global one when channelless; strangers still sync nothing
|
|
49
36
|
this._topic = crypto.hash(b4a.from(handle.network.channel || 'cero-ble'))
|
|
50
37
|
this.swarm = new BluetoothSwarm({
|
|
51
38
|
backend,
|
|
52
|
-
//
|
|
53
|
-
// swarm, so one person reached over Wi-Fi and BLE dedupes to one peer
|
|
39
|
+
// the swarm identity, so one person over Wi-Fi and BLE dedupes to one peer
|
|
54
40
|
keyPair: { publicKey: identity.publicKey, secretKey: identity.secretKey },
|
|
55
41
|
topic: this._topic,
|
|
56
42
|
tag: 'cero-ble',
|
|
@@ -59,9 +45,7 @@ export class Bluetooth extends ReadyResource {
|
|
|
59
45
|
maxInbound
|
|
60
46
|
})
|
|
61
47
|
this.swarm.on('update', () => {
|
|
62
|
-
//
|
|
63
|
-
// drain — retuning drops links, and the pairing link still carries
|
|
64
|
-
// the joiner's initial replication
|
|
48
|
+
// retune to the mesh only once the pairing link's initial replication drained
|
|
65
49
|
if (this._restorePending && this.swarm.peers.size === 0) {
|
|
66
50
|
this._restorePending = false
|
|
67
51
|
this.swarm.setTopic(this._topic).catch(safetyCatch)
|
|
@@ -69,7 +53,6 @@ export class Bluetooth extends ReadyResource {
|
|
|
69
53
|
this.emit('update')
|
|
70
54
|
})
|
|
71
55
|
this.swarm.on('connection', (conn) => {
|
|
72
|
-
// the same treatment as any transport: wakeup, replication, pairing
|
|
73
56
|
this._handle.network.inject(conn)
|
|
74
57
|
})
|
|
75
58
|
}
|
|
@@ -88,6 +71,11 @@ export class Bluetooth extends ReadyResource {
|
|
|
88
71
|
if (this._autoStart) await this.start()
|
|
89
72
|
}
|
|
90
73
|
|
|
74
|
+
async _close() {
|
|
75
|
+
this._clearAnnounce()
|
|
76
|
+
await this.swarm.destroy()
|
|
77
|
+
}
|
|
78
|
+
|
|
91
79
|
/**
|
|
92
80
|
* Begin advertising + scanning. Idempotent; no-op when unsupported.
|
|
93
81
|
*
|
|
@@ -98,34 +86,20 @@ export class Bluetooth extends ReadyResource {
|
|
|
98
86
|
}
|
|
99
87
|
|
|
100
88
|
/**
|
|
101
|
-
* Stop advertising/scanning and drop links; open invite rendezvous end with
|
|
102
|
-
* the radio. Idempotent. Local data and the rest of the network (DHT) are
|
|
103
|
-
* untouched.
|
|
89
|
+
* Stop advertising/scanning and drop links; open invite rendezvous end with the radio.
|
|
104
90
|
*
|
|
105
91
|
* @returns {Promise<void>}
|
|
106
92
|
*/
|
|
107
93
|
async stop() {
|
|
108
94
|
this._clearAnnounce()
|
|
109
95
|
await this.swarm.stop()
|
|
110
|
-
// while stopped setTopic
|
|
111
|
-
// mesh. A start() + announce() that landed during the await wins.
|
|
96
|
+
// while stopped setTopic sticks; a start() + announce() during the await wins
|
|
112
97
|
if (!this._announce) await this.swarm.setTopic(this._topic)
|
|
113
98
|
}
|
|
114
99
|
|
|
115
100
|
/**
|
|
116
|
-
* Offline join rendezvous: retune the radio to the invite-derived topic so
|
|
117
|
-
*
|
|
118
|
-
* announcing a new invite replaces the previous rendezvous. Returns a stop
|
|
119
|
-
* function — closing the QR must stop the rendezvous so a photographed
|
|
120
|
-
* invite doesn't stay an ambient discovery beacon (admission itself is
|
|
121
|
-
* always gated by blind-pairing verifying the invite). The retune back to
|
|
122
|
-
* the mesh topic waits for live links to drain: the link a join just
|
|
123
|
-
* established survives and carries the joiner's initial replication.
|
|
124
|
-
* Auto-stops at the invite's expiry, on `stop()`, and on close.
|
|
125
|
-
*
|
|
126
|
-
* Only active while nearby sync is on: before `start()` (and after `stop()`)
|
|
127
|
-
* this is a no-op — the user controls the radio, and a join must not touch
|
|
128
|
-
* Bluetooth (OS permissions, GATT server) they never enabled.
|
|
101
|
+
* Offline join rendezvous: retune the radio to the invite-derived topic so holder and
|
|
102
|
+
* joiner find each other with zero DHT.
|
|
129
103
|
*
|
|
130
104
|
* @param {string} invite Z32 invite string.
|
|
131
105
|
* @returns {() => void}
|
|
@@ -161,20 +135,6 @@ export class Bluetooth extends ReadyResource {
|
|
|
161
135
|
}
|
|
162
136
|
}
|
|
163
137
|
|
|
164
|
-
_stopAnnounce() {
|
|
165
|
-
this._clearAnnounce()
|
|
166
|
-
if (this.swarm.peers.size > 0) this._restorePending = true
|
|
167
|
-
else this.swarm.setTopic(this._topic).catch(safetyCatch)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
_clearAnnounce() {
|
|
171
|
-
const e = this._announce
|
|
172
|
-
if (!e) return
|
|
173
|
-
if (e.timer) clearTimeout(e.timer)
|
|
174
|
-
this._announce = null
|
|
175
|
-
this._restorePending = false
|
|
176
|
-
}
|
|
177
|
-
|
|
178
138
|
/**
|
|
179
139
|
* Host-lifecycle pause (app backgrounded): radio down, user intent kept.
|
|
180
140
|
*
|
|
@@ -193,8 +153,17 @@ export class Bluetooth extends ReadyResource {
|
|
|
193
153
|
await this.swarm.resume()
|
|
194
154
|
}
|
|
195
155
|
|
|
196
|
-
|
|
156
|
+
_stopAnnounce() {
|
|
197
157
|
this._clearAnnounce()
|
|
198
|
-
|
|
158
|
+
if (this.swarm.peers.size > 0) this._restorePending = true
|
|
159
|
+
else this.swarm.setTopic(this._topic).catch(safetyCatch)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_clearAnnounce() {
|
|
163
|
+
const e = this._announce
|
|
164
|
+
if (!e) return
|
|
165
|
+
if (e.timer) clearTimeout(e.timer)
|
|
166
|
+
this._announce = null
|
|
167
|
+
this._restorePending = false
|
|
199
168
|
}
|
|
200
169
|
}
|
package/src/lib/constants.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Shared cero constants.
|
|
2
|
-
|
|
3
1
|
export const NS = 'cero'
|
|
4
2
|
export const COUNTERS = 'counters'
|
|
5
3
|
export const EPOCHS = 'epochs'
|
|
@@ -7,16 +5,3 @@ export const EPOCHS = 'epochs'
|
|
|
7
5
|
// Writer-admission / pairing timeout (ms), and the initial discovery-flush wait (ms).
|
|
8
6
|
export const TIMEOUT = 30000
|
|
9
7
|
export const FLUSH = 500
|
|
10
|
-
|
|
11
|
-
// schema-DSL primitive → HyperDB type (used by the builder).
|
|
12
|
-
export const DB_TYPE = {
|
|
13
|
-
string: 'string',
|
|
14
|
-
uint: 'uint',
|
|
15
|
-
int: 'int',
|
|
16
|
-
bool: 'bool',
|
|
17
|
-
bytes: 'buffer',
|
|
18
|
-
json: 'json',
|
|
19
|
-
fixed32: 'fixed32',
|
|
20
|
-
fixed64: 'fixed64',
|
|
21
|
-
file: 'string'
|
|
22
|
-
}
|