@cero-base/cero 1.11.1 → 1.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cero-base/cero",
3
- "version": "1.11.1",
4
- "description": "The ideal p2p API \u2014 everything is a handle, handles contain refs, refs contain rows.",
3
+ "version": "1.13.0",
4
+ "description": "The ideal p2p API everything is a handle, handles contain refs, refs contain rows.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "engines": {
@@ -95,19 +95,20 @@
95
95
  "test:node": "ls test/*.test.js | xargs -P1 -n1 brittle-node"
96
96
  },
97
97
  "dependencies": {
98
- "@cero-base/core": "^1.11.0",
98
+ "@cero-base/core": "^1.13.0",
99
99
  "b4a": "^1.8.1",
100
100
  "bare-abort-controller": "^1.1.2",
101
101
  "bare-crypto": "^1.15.3",
102
102
  "bare-fs": "^4.8.0",
103
103
  "bare-path": "^3.1.1",
104
- "compact-encoding": "^3.3.0",
104
+ "ble-swarm": "github:holepunchto/ble-swarm#63566f02d4b3a1f3849b9fb009e18666411592a5",
105
+ "compact-encoding": "^3.3.2",
105
106
  "corestore": "^7.12.0",
106
- "hrpc": "^4.3.0",
107
- "hypercore": "^11.35.1",
107
+ "hrpc": "^4.3.1",
108
+ "hypercore": "^11.35.2",
108
109
  "hypercore-crypto": "^3.7.0",
109
- "hypercore-storage": "^3.2.0",
110
- "hyperdb": "^6.8.0",
110
+ "hypercore-storage": "^3.2.1",
111
+ "hyperdb": "^6.9.0",
111
112
  "hyperdispatch": "^1.6.0",
112
113
  "hyperschema": "^1.22.0",
113
114
  "ready-resource": "^1.2.0",
@@ -124,14 +125,5 @@
124
125
  "brittle": "^4.1.0",
125
126
  "typescript": "^5.9.3"
126
127
  },
127
- "license": "Apache-2.0",
128
- "x": 1,
129
- "peerDependencies": {
130
- "bare-bluetooth": ">=0.2.0"
131
- },
132
- "peerDependenciesMeta": {
133
- "bare-bluetooth": {
134
- "optional": true
135
- }
136
- }
128
+ "license": "Apache-2.0"
137
129
  }
package/src/bluetooth.js CHANGED
@@ -1,33 +1,22 @@
1
1
  import ReadyResource from 'ready-resource'
2
+ import BluetoothSwarm from 'ble-swarm'
3
+ import crypto from 'hypercore-crypto'
2
4
  import safetyCatch from 'safety-catch'
5
+ import b4a from 'b4a'
3
6
 
4
- import { BLETransport } from '@cero-base/core/network/transports/ble'
5
7
  import { Pairing } from '@cero-base/core/pairing'
6
8
  import { Invite } from '@cero-base/core/invite'
7
9
 
8
10
  /**
9
- * Lazily resolve the bare-bluetooth backend. Absent (Linux, or dep not
10
- * bundled) null, which the facade reports as `state: 'unsupported'`.
11
- * Never throws a missing optional dep is a state, not a crash.
12
- *
13
- * @returns {Promise<any | null>}
14
- */
15
- async function loadBackend() {
16
- try {
17
- return await import('bare-bluetooth')
18
- } catch {
19
- return null
20
- }
21
- }
22
-
23
- /**
24
- * `me.bluetooth` — the whole app-facing surface for nearby (Bluetooth) sync.
25
- * Bluetooth is a cero feature, not a parallel API: it only changes how peers
26
- * meet and carry bytes; capability-gated replication still decides what syncs.
11
+ * `me.bluetooth` the app-facing surface for nearby (Bluetooth) sync, a thin
12
+ * facade over ble-swarm. Bluetooth only changes how peers meet and carry
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.
27
16
  *
28
17
  * ```js
29
18
  * const me = await cero(dir, spec, { channel, bluetooth: true })
30
- * me.bluetooth.state // 'unsupported' | 'unauthorized' | 'off' | 'waiting' | 'on'
19
+ * me.bluetooth.state // 'unsupported' | 'unauthorized' | 'off' | 'waiting' | 'starting' | 'on'
31
20
  * await me.bluetooth.start()
32
21
  * me.bluetooth.peers // Map of live BLE links
33
22
  * me.bluetooth.on('update', () => {})
@@ -39,7 +28,7 @@ export class Bluetooth extends ReadyResource {
39
28
  /**
40
29
  * @param {object} handle Root cero Handle (network + identity + channel).
41
30
  * @param {object} [opts]
42
- * @param {any} [opts.backend] Injected bare-bluetooth-shaped backend (tests); lazy-loaded when absent.
31
+ * @param {any} [opts.backend] Injected bare-bluetooth-shaped backend (tests); omitted → ble-swarm loads its own, null/false → unsupported.
43
32
  * @param {boolean} [opts.autoStart] Start on handle open (from `cero({ bluetooth: true })`).
44
33
  * @param {number} [opts.maxOutbound] Max concurrent outbound links; gossip covers the rest.
45
34
  * @param {number} [opts.maxInbound] Max concurrent inbound sessions; newcomers past this are refused.
@@ -48,28 +37,90 @@ export class Bluetooth extends ReadyResource {
48
37
  constructor(handle, { backend, autoStart, maxOutbound, maxInbound, pipe } = {}) {
49
38
  super()
50
39
  this._handle = handle
51
- // explicit null/false disables bluetooth; only an omitted backend lazy-loads
52
- this._backend = backend || null
53
- this._lazy = backend === undefined
54
40
  this._autoStart = autoStart === true
55
- this._maxOutbound = maxOutbound
56
- this._maxInbound = maxInbound
57
- this._pipe = pipe
58
- this._transport = null
59
- this._name = null
60
- this._announces = new Set()
61
- /** @type {'unsupported'|'unauthorized'|'off'|'waiting'|'starting'|'on'} */
62
- this.state = 'off'
41
+ /** @type {{ hex: string, count: number, timer: any } | null} active invite rendezvous (single topic — one at a time) */
42
+ this._announce = null
43
+ this._restorePending = false
44
+
45
+ const identity = handle.identity
46
+ // the mesh topic: one per channel, or a fixed global topic when
47
+ // channelless any nearby cero device links (global nearby; strangers
48
+ // still sync zero bytes, replication is capability-gated)
49
+ this._topic = crypto.hash(b4a.from(handle.network.channel || 'cero-ble'))
50
+ this.swarm = new BluetoothSwarm({
51
+ backend,
52
+ // injected links authenticate with the same long-lived identity as the
53
+ // swarm, so one person reached over Wi-Fi and BLE dedupes to one peer
54
+ keyPair: { publicKey: identity.publicKey, secretKey: identity.secretKey },
55
+ topic: this._topic,
56
+ tag: 'cero-ble',
57
+ pipe,
58
+ maxOutbound,
59
+ maxInbound
60
+ })
61
+ this.swarm.on('update', () => {
62
+ // an ended rendezvous retunes back to the mesh only once its links
63
+ // drain — retuning drops links, and the pairing link still carries
64
+ // the joiner's initial replication
65
+ if (this._restorePending && this.swarm.peers.size === 0) {
66
+ this._restorePending = false
67
+ this.swarm.setTopic(this._topic).catch(safetyCatch)
68
+ }
69
+ this.emit('update')
70
+ })
71
+ this.swarm.on('connection', (conn) => {
72
+ // the same treatment as any transport: wakeup, replication, pairing
73
+ this._handle.network.inject(conn)
74
+ })
75
+ }
76
+
77
+ /** @returns {'unsupported'|'unauthorized'|'off'|'waiting'|'starting'|'on'} */
78
+ get state() {
79
+ return this.swarm.state
80
+ }
81
+
82
+ /** @returns {Map<string, any>} Live BLE links, keyed by peer public key. */
83
+ get peers() {
84
+ return this.swarm.peers
85
+ }
86
+
87
+ async _open() {
88
+ if (this._autoStart) await this.start()
89
+ }
90
+
91
+ /**
92
+ * Begin advertising + scanning. Idempotent; no-op when unsupported.
93
+ *
94
+ * @returns {Promise<void>}
95
+ */
96
+ async start() {
97
+ await this.swarm.start()
63
98
  }
64
99
 
65
100
  /**
66
- * Offline join rendezvous. Both sides derive the same BLE service UUID from
67
- * the invite, so they find each other with zero DHT: the host calls this
68
- * while the invite QR is on screen; the joiner's `open(me.room, invite)`
69
- * calls it automatically for the duration of the join. Returns a stop
70
- * function — closing the QR must stop the advertisement so a photographed
71
- * invite doesn't stay an ambient admission ticket. Auto-stops at the
72
- * invite's expiry, on `bluetooth.stop()`, and on close.
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.
104
+ *
105
+ * @returns {Promise<void>}
106
+ */
107
+ async stop() {
108
+ this._clearAnnounce()
109
+ await this.swarm.stop()
110
+ // while stopped setTopic just sticks — the next start() is back on the mesh
111
+ await this.swarm.setTopic(this._topic)
112
+ }
113
+
114
+ /**
115
+ * Offline join rendezvous: retune the radio to the invite-derived topic so
116
+ * holder and joiner find each other with zero DHT. One topic at a time —
117
+ * announcing a new invite replaces the previous rendezvous. Returns a stop
118
+ * function — closing the QR must stop the rendezvous so a photographed
119
+ * invite doesn't stay an ambient discovery beacon (admission itself is
120
+ * always gated by blind-pairing verifying the invite). The retune back to
121
+ * the mesh topic waits for live links to drain: the link a join just
122
+ * established survives and carries the joiner's initial replication.
123
+ * Auto-stops at the invite's expiry, on `stop()`, and on close.
73
124
  *
74
125
  * Only active while nearby sync is on: before `start()` (and after `stop()`)
75
126
  * this is a no-op — the user controls the radio, and a join must not touch
@@ -79,138 +130,70 @@ export class Bluetooth extends ReadyResource {
79
130
  * @returns {() => void}
80
131
  */
81
132
  announce(invite) {
82
- if (!this._backend || this.state === 'unsupported') return () => {}
83
- if (!this._transport || (this.state !== 'on' && this.state !== 'waiting' && this.state !== 'starting'))
133
+ if (!this.swarm.supported) return () => {}
134
+ if (this.state !== 'on' && this.state !== 'waiting' && this.state !== 'starting') {
84
135
  return () => {}
136
+ }
85
137
  const topic = Pairing.inviteTopic(invite)
86
138
  if (!topic) return () => {}
87
139
 
88
- const transport = new BLETransport({
89
- backend: this._backend,
90
- network: this._handle.network,
91
- uuid: topic,
92
- nodeId: this._handle.identity.publicKey,
93
- tag: 'cero-ble-invite',
94
- pipe: this._pipe,
95
- // the QR closing stops the rendezvous, not the just-established link —
96
- // that link carries the joiner's initial replication
97
- keepLinks: true
98
- })
99
- transport.ready().catch(safetyCatch)
100
- this._announces.add(transport)
101
-
102
- const { expires } = Invite.parse(invite)
103
- const timer = expires > 0 ? setTimeout(() => stop(), Math.max(0, expires - Date.now())) : null
140
+ const hex = b4a.toString(topic, 'hex')
141
+ if (this._announce && this._announce.hex !== hex) this._stopAnnounce()
142
+ if (!this._announce) {
143
+ const entry = { hex, count: 0, timer: null }
144
+ const { expires } = Invite.parse(invite)
145
+ if (expires > 0) {
146
+ entry.timer = setTimeout(() => this._stopAnnounce(), Math.max(0, expires - Date.now()))
147
+ }
148
+ this._announce = entry
149
+ this._restorePending = false
150
+ this.swarm.setTopic(topic).catch(safetyCatch)
151
+ }
152
+ const entry = this._announce
153
+ entry.count++
104
154
 
105
- const stop = () => {
106
- if (timer) clearTimeout(timer)
107
- if (!this._announces.delete(transport)) return
108
- transport.close().catch(safetyCatch)
155
+ let stopped = false
156
+ return () => {
157
+ if (stopped) return
158
+ stopped = true
159
+ if (this._announce === entry && --entry.count <= 0) this._stopAnnounce()
109
160
  }
110
- return stop
111
161
  }
112
162
 
113
- /** @returns {Map<string, any>} Live BLE links, keyed by peer node id. */
114
- get peers() {
115
- return this._transport ? this._transport.peers : new Map()
163
+ _stopAnnounce() {
164
+ this._clearAnnounce()
165
+ if (this.swarm.peers.size > 0) this._restorePending = true
166
+ else this.swarm.setTopic(this._topic).catch(safetyCatch)
116
167
  }
117
168
 
118
- async _open() {
119
- if (this._backend === null && this._lazy) this._backend = await loadBackend()
120
- if (!this._backend) {
121
- this.state = 'unsupported'
122
- return
123
- }
124
- if (this._autoStart) await this.start()
169
+ _clearAnnounce() {
170
+ const e = this._announce
171
+ if (!e) return
172
+ if (e.timer) clearTimeout(e.timer)
173
+ this._announce = null
174
+ this._restorePending = false
125
175
  }
126
176
 
127
177
  /**
128
- * Begin advertising + scanning on the channel-derived UUID. Idempotent.
129
- * No-op (stays `unsupported`) when no backend is present.
178
+ * Host-lifecycle pause (app backgrounded): radio down, user intent kept.
130
179
  *
131
180
  * @returns {Promise<void>}
132
181
  */
133
- async start({ name } = {}) {
134
- if (name !== undefined) this._name = name
135
- if (this.state === 'unsupported') return
136
- if (this._transport) {
137
- // reuse one transport across toggles — recreating leaks iOS CoreBluetooth
138
- // managers (can't destroy()) and their stale GATT service
139
- this._transport.name = this._name || ''
140
- this._transport.resume()
141
- this.state = this._transport.state
142
- this.emit('update')
143
- return
144
- }
145
- const handle = this._handle
146
-
147
- this._transport = new BLETransport({
148
- backend: this._backend,
149
- network: handle.network,
150
- // channel isolation for free: same channel → same UUID, like the swarm.
151
- // No channel → the identity topic (unchanged global-mesh semantics).
152
- uuid: handle.network.channel
153
- ? Buffer.from(handle.network.channel)
154
- : handle.identity.publicKey,
155
- nodeId: handle.identity.publicKey,
156
- name: this._name || '',
157
- pipe: this._pipe,
158
- maxOutbound: this._maxOutbound,
159
- maxInbound: this._maxInbound,
160
- // Android scans in a battery-saver mode by default — too slow for a mesh
161
- scanOptions:
162
- typeof Bare !== 'undefined' && Bare.platform === 'android'
163
- ? { scanMode: this._backend.Central.SCAN_MODE_LOW_LATENCY }
164
- : undefined
165
- })
166
- const transport = this._transport
167
- transport.on('update', () => {
168
- // a torn-down or replaced transport can still emit late adapter events
169
- if (this._transport !== transport) return
170
- this.state = transport.state
171
- this.emit('update')
172
- })
173
- try {
174
- await this._transport.ready()
175
- this.state = this._transport.state
176
- } catch (err) {
177
- this._transport = null
178
- this.state = 'off'
179
- throw err
180
- }
181
- this.emit('update')
182
+ async suspend() {
183
+ await this.swarm.suspend()
182
184
  }
183
185
 
184
186
  /**
185
- * Stop advertising/scanning and drop links. Idempotent. Sync stops; local
186
- * data and the rest of the network (DHT) are untouched.
187
+ * Resume after a host-lifecycle pause.
187
188
  *
188
189
  * @returns {Promise<void>}
189
190
  */
190
- async stop() {
191
- for (const t of [...this._announces]) {
192
- this._announces.delete(t)
193
- await t.close().catch(safetyCatch)
194
- }
195
- if (!this._transport) return
196
- // suspend, don't close: keep the transport (and its GATT service) so a later
197
- // start() resumes the same instance instead of leaking a new one. suspend()
198
- // is async (it says goodbye + drains before hanging up); await it.
199
- await this._transport.suspend()
200
- this.state = 'off'
201
- this.emit('update')
191
+ async resume() {
192
+ await this.swarm.resume()
202
193
  }
203
194
 
204
195
  async _close() {
205
- for (const t of [...this._announces]) {
206
- this._announces.delete(t)
207
- await t.close().catch(safetyCatch)
208
- }
209
- if (this._transport) {
210
- await this._transport.close().catch(safetyCatch)
211
- this._transport = null
212
- }
213
- this.state = 'off'
214
- this.emit('update')
196
+ this._clearAnnounce()
197
+ await this.swarm.destroy()
215
198
  }
216
199
  }
@@ -837,6 +837,7 @@ export class Handle extends ReadyResource {
837
837
  if (this.parent || this.closing || this.closed || this._suspended) return
838
838
  this._suspended = true
839
839
  await Promise.all([...this.children].map((c) => c.pair?.suspend()))
840
+ await this.bluetooth?.suspend()
840
841
  await this.network.suspend()
841
842
  try {
842
843
  await this.store.store.suspend()
@@ -859,6 +860,7 @@ export class Handle extends ReadyResource {
859
860
  this._onerror(err)
860
861
  }
861
862
  await this.network.resume()
863
+ await this.bluetooth?.resume()
862
864
  await Promise.all([...this.children].map((child) => child.pair?.resume()))
863
865
  }
864
866
 
package/src/index.js CHANGED
@@ -202,7 +202,8 @@ export async function cero(dir, spec, opts = {}) {
202
202
  backend: bt.backend,
203
203
  autoStart: bt.autoStart !== false,
204
204
  maxOutbound: bt.maxOutbound,
205
- maxInbound: bt.maxInbound
205
+ maxInbound: bt.maxInbound,
206
+ pipe: bt.pipe
206
207
  })
207
208
  me.once('close', () => me.bluetooth.close().catch(safetyCatch))
208
209
  await me.bluetooth.ready()
@@ -1,11 +1,13 @@
1
1
  /**
2
- * `me.bluetooth` — the whole app-facing surface for nearby (Bluetooth) sync.
3
- * Bluetooth is a cero feature, not a parallel API: it only changes how peers
4
- * meet and carry bytes; capability-gated replication still decides what syncs.
2
+ * `me.bluetooth` — the app-facing surface for nearby (Bluetooth) sync, a thin
3
+ * facade over ble-swarm. Bluetooth only changes how peers meet and carry
4
+ * bytes; capability-gated replication still decides what syncs. Discovery is
5
+ * one topic-derived service UUID at a time (tag `cero-ble`) — the data service
6
+ * sits on a fixed per-tag UUID, so switching topics only retunes the radio.
5
7
  *
6
8
  * ```js
7
9
  * const me = await cero(dir, spec, { channel, bluetooth: true })
8
- * me.bluetooth.state // 'unsupported' | 'unauthorized' | 'off' | 'waiting' | 'on'
10
+ * me.bluetooth.state // 'unsupported' | 'unauthorized' | 'off' | 'waiting' | 'starting' | 'on'
9
11
  * await me.bluetooth.start()
10
12
  * me.bluetooth.peers // Map of live BLE links
11
13
  * me.bluetooth.on('update', () => {})
@@ -17,7 +19,7 @@ export class Bluetooth extends ReadyResource {
17
19
  /**
18
20
  * @param {object} handle Root cero Handle (network + identity + channel).
19
21
  * @param {object} [opts]
20
- * @param {any} [opts.backend] Injected bare-bluetooth-shaped backend (tests); lazy-loaded when absent.
22
+ * @param {any} [opts.backend] Injected bare-bluetooth-shaped backend (tests); omitted → ble-swarm loads its own, null/false → unsupported.
21
23
  * @param {boolean} [opts.autoStart] Start on handle open (from `cero({ bluetooth: true })`).
22
24
  * @param {number} [opts.maxOutbound] Max concurrent outbound links; gossip covers the rest.
23
25
  * @param {number} [opts.maxInbound] Max concurrent inbound sessions; newcomers past this are refused.
@@ -31,25 +33,44 @@ export class Bluetooth extends ReadyResource {
31
33
  pipe?: "l2cap" | "gatt";
32
34
  });
33
35
  _handle: any;
34
- _backend: any;
35
- _lazy: boolean;
36
36
  _autoStart: boolean;
37
- _maxOutbound: number;
38
- _maxInbound: number;
39
- _pipe: "l2cap" | "gatt";
40
- _transport: BLETransport;
41
- _name: any;
42
- _announces: Set<any>;
43
- /** @type {'unsupported'|'unauthorized'|'off'|'waiting'|'starting'|'on'} */
44
- state: "unsupported" | "unauthorized" | "off" | "waiting" | "starting" | "on";
37
+ /** @type {{ hex: string, count: number, timer: any } | null} active invite rendezvous (single topic — one at a time) */
38
+ _announce: {
39
+ hex: string;
40
+ count: number;
41
+ timer: any;
42
+ } | null;
43
+ _restorePending: boolean;
44
+ _topic: any;
45
+ swarm: any;
46
+ /** @returns {'unsupported'|'unauthorized'|'off'|'waiting'|'starting'|'on'} */
47
+ get state(): "unsupported" | "unauthorized" | "off" | "waiting" | "starting" | "on";
48
+ /** @returns {Map<string, any>} Live BLE links, keyed by peer public key. */
49
+ get peers(): Map<string, any>;
50
+ /**
51
+ * Begin advertising + scanning. Idempotent; no-op when unsupported.
52
+ *
53
+ * @returns {Promise<void>}
54
+ */
55
+ start(): Promise<void>;
56
+ /**
57
+ * Stop advertising/scanning and drop links; open invite rendezvous end with
58
+ * the radio. Idempotent. Local data and the rest of the network (DHT) are
59
+ * untouched.
60
+ *
61
+ * @returns {Promise<void>}
62
+ */
63
+ stop(): Promise<void>;
45
64
  /**
46
- * Offline join rendezvous. Both sides derive the same BLE service UUID from
47
- * the invite, so they find each other with zero DHT: the host calls this
48
- * while the invite QR is on screen; the joiner's `open(me.room, invite)`
49
- * calls it automatically for the duration of the join. Returns a stop
50
- * function closing the QR must stop the advertisement so a photographed
51
- * invite doesn't stay an ambient admission ticket. Auto-stops at the
52
- * invite's expiry, on `bluetooth.stop()`, and on close.
65
+ * Offline join rendezvous: retune the radio to the invite-derived topic so
66
+ * holder and joiner find each other with zero DHT. One topic at a time —
67
+ * announcing a new invite replaces the previous rendezvous. Returns a stop
68
+ * function closing the QR must stop the rendezvous so a photographed
69
+ * invite doesn't stay an ambient discovery beacon (admission itself is
70
+ * always gated by blind-pairing verifying the invite). The retune back to
71
+ * the mesh topic waits for live links to drain: the link a join just
72
+ * established survives and carries the joiner's initial replication.
73
+ * Auto-stops at the invite's expiry, on `stop()`, and on close.
53
74
  *
54
75
  * Only active while nearby sync is on: before `start()` (and after `stop()`)
55
76
  * this is a no-op — the user controls the radio, and a join must not touch
@@ -59,22 +80,19 @@ export class Bluetooth extends ReadyResource {
59
80
  * @returns {() => void}
60
81
  */
61
82
  announce(invite: string): () => void;
62
- /** @returns {Map<string, any>} Live BLE links, keyed by peer node id. */
63
- get peers(): Map<string, any>;
83
+ _stopAnnounce(): void;
84
+ _clearAnnounce(): void;
64
85
  /**
65
- * Begin advertising + scanning on the channel-derived UUID. Idempotent.
66
- * No-op (stays `unsupported`) when no backend is present.
86
+ * Host-lifecycle pause (app backgrounded): radio down, user intent kept.
67
87
  *
68
88
  * @returns {Promise<void>}
69
89
  */
70
- start({ name }?: {}): Promise<void>;
90
+ suspend(): Promise<void>;
71
91
  /**
72
- * Stop advertising/scanning and drop links. Idempotent. Sync stops; local
73
- * data and the rest of the network (DHT) are untouched.
92
+ * Resume after a host-lifecycle pause.
74
93
  *
75
94
  * @returns {Promise<void>}
76
95
  */
77
- stop(): Promise<void>;
96
+ resume(): Promise<void>;
78
97
  }
79
98
  import ReadyResource from 'ready-resource';
80
- import { BLETransport } from '@cero-base/core/network/transports/ble';