@cero-base/cero 1.2.0 → 1.4.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,6 +1,6 @@
1
1
  {
2
2
  "name": "@cero-base/cero",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "The ideal p2p API — everything is a handle, handles contain refs, refs contain rows.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -86,7 +86,7 @@
86
86
  "test:bare": "npx bare test/bare-smoke.js"
87
87
  },
88
88
  "dependencies": {
89
- "@cero-base/core": "^1.2.0",
89
+ "@cero-base/core": "^1.4.0",
90
90
  "b4a": "^1.8.1",
91
91
  "bare-abort-controller": "^1.1.2",
92
92
  "bare-crypto": "^1.15.3",
package/src/bluetooth.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import ReadyResource from 'ready-resource'
2
2
  import safetyCatch from 'safety-catch'
3
3
 
4
- import { BluetoothTransport } from '@cero-base/core/network/bluetooth'
4
+ import { BLETransport } from '@cero-base/core/network/transports/ble'
5
5
  import { Pairing } from '@cero-base/core/pairing'
6
6
  import { Invite } from '@cero-base/core/invite'
7
7
 
@@ -48,6 +48,7 @@ export class Bluetooth extends ReadyResource {
48
48
  this._backend = backend || null
49
49
  this._autoStart = autoStart === true
50
50
  this._transport = null
51
+ this._name = null
51
52
  this._announces = new Set()
52
53
  /** @type {'unsupported'|'unauthorized'|'off'|'waiting'|'starting'|'on'} */
53
54
  this.state = 'off'
@@ -70,7 +71,7 @@ export class Bluetooth extends ReadyResource {
70
71
  const topic = Pairing.inviteTopic(invite)
71
72
  if (!topic) return () => {}
72
73
 
73
- const transport = new BluetoothTransport({
74
+ const transport = new BLETransport({
74
75
  backend: this._backend,
75
76
  network: this._handle.network,
76
77
  uuid: topic,
@@ -114,12 +115,21 @@ export class Bluetooth extends ReadyResource {
114
115
  *
115
116
  * @returns {Promise<void>}
116
117
  */
117
- async start() {
118
+ async start({ name } = {}) {
119
+ if (name !== undefined) this._name = name
118
120
  if (this.state === 'unsupported') return
119
- if (this._transport) return
121
+ if (this._transport) {
122
+ // reuse one transport across toggles — recreating leaks iOS CoreBluetooth
123
+ // managers (can't destroy()) and their stale GATT service
124
+ this._transport.name = this._name || ''
125
+ this._transport.resume()
126
+ this.state = this._transport.state
127
+ this.emit('update')
128
+ return
129
+ }
120
130
  const handle = this._handle
121
131
 
122
- this._transport = new BluetoothTransport({
132
+ this._transport = new BLETransport({
123
133
  backend: this._backend,
124
134
  network: handle.network,
125
135
  // channel isolation for free: same channel → same UUID, like the swarm.
@@ -127,7 +137,13 @@ export class Bluetooth extends ReadyResource {
127
137
  uuid: handle.network.channel
128
138
  ? Buffer.from(handle.network.channel)
129
139
  : handle.identity.publicKey,
130
- nodeId: handle.identity.publicKey
140
+ nodeId: handle.identity.publicKey,
141
+ name: this._name || '',
142
+ // Android scans in a battery-saver mode by default — too slow for a mesh
143
+ scanOptions:
144
+ typeof Bare !== 'undefined' && Bare.platform === 'android'
145
+ ? { scanMode: this._backend.Central.SCAN_MODE_LOW_LATENCY }
146
+ : undefined
131
147
  })
132
148
  this._transport.on('update', () => {
133
149
  this.state = this._transport.state
@@ -156,17 +172,24 @@ export class Bluetooth extends ReadyResource {
156
172
  await t.close().catch(safetyCatch)
157
173
  }
158
174
  if (!this._transport) return
159
- try {
160
- await this._transport.close()
161
- } catch (err) {
162
- safetyCatch(err)
163
- }
164
- this._transport = null
175
+ // suspend, don't close: keep the transport (and its GATT service) so a later
176
+ // start() resumes the same instance instead of leaking a new one. suspend()
177
+ // is async (it says goodbye + drains before hanging up); await it.
178
+ await this._transport.suspend()
165
179
  this.state = 'off'
166
180
  this.emit('update')
167
181
  }
168
182
 
169
183
  async _close() {
170
- await this.stop()
184
+ for (const t of [...this._announces]) {
185
+ this._announces.delete(t)
186
+ await t.close().catch(safetyCatch)
187
+ }
188
+ if (this._transport) {
189
+ await this._transport.close().catch(safetyCatch)
190
+ this._transport = null
191
+ }
192
+ this.state = 'off'
193
+ this.emit('update')
171
194
  }
172
195
  }
package/src/index.js CHANGED
@@ -69,7 +69,7 @@ export { t, schema } from './lib/spec.js'
69
69
  * @property {number} [recoveryTimeout] Max wait for peer data + writer capability during recovery.
70
70
  * @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.
71
71
  * @property {boolean} [extensions] `false` disables the bundled extensions (profileSync, handleSync) for this instance. Build with `{ extensions: false }` too so the spec matches.
72
- * @property {boolean | any} [bluetooth] `true` enables nearby (Bluetooth) sync via `me.bluetooth`; pass a bare-bluetooth-shaped backend to inject one. Absent backend → `me.bluetooth.state === 'unsupported'`.
72
+ * @property {boolean | { autoStart?: boolean, backend?: any }} [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); absent backend on an unsupported host → `me.bluetooth.state === 'unsupported'`.
73
73
  */
74
74
 
75
75
  /**
@@ -177,9 +177,17 @@ export async function cero(dir, spec, opts = {}) {
177
177
  }
178
178
 
179
179
  if (opts.bluetooth) {
180
+ // `true` → defaults; `{ autoStart, backend }` → options. A bare backend
181
+ // object (pre-1.3 shape, has Central/Server) is still accepted.
182
+ const bt =
183
+ opts.bluetooth === true
184
+ ? {}
185
+ : opts.bluetooth.Central
186
+ ? { backend: opts.bluetooth }
187
+ : opts.bluetooth
180
188
  me.bluetooth = new Bluetooth(me, {
181
- backend: opts.bluetooth === true ? null : opts.bluetooth,
182
- autoStart: true
189
+ backend: bt.backend || null,
190
+ autoStart: bt.autoStart !== false
183
191
  })
184
192
  me.once('close', () => me.bluetooth.close().catch(safetyCatch))
185
193
  await me.bluetooth.ready()
@@ -27,7 +27,8 @@ export class Bluetooth extends ReadyResource {
27
27
  _handle: any;
28
28
  _backend: any;
29
29
  _autoStart: boolean;
30
- _transport: BluetoothTransport;
30
+ _transport: BLETransport;
31
+ _name: any;
31
32
  _announces: Set<any>;
32
33
  /** @type {'unsupported'|'unauthorized'|'off'|'waiting'|'starting'|'on'} */
33
34
  state: "unsupported" | "unauthorized" | "off" | "waiting" | "starting" | "on";
@@ -52,7 +53,7 @@ export class Bluetooth extends ReadyResource {
52
53
  *
53
54
  * @returns {Promise<void>}
54
55
  */
55
- start(): Promise<void>;
56
+ start({ name }?: {}): Promise<void>;
56
57
  /**
57
58
  * Stop advertising/scanning and drop links. Idempotent. Sync stops; local
58
59
  * data and the rest of the network (DHT) are untouched.
@@ -62,4 +63,4 @@ export class Bluetooth extends ReadyResource {
62
63
  stop(): Promise<void>;
63
64
  }
64
65
  import ReadyResource from 'ready-resource';
65
- import { BluetoothTransport } from '@cero-base/core/network/bluetooth';
66
+ import { BLETransport } from '@cero-base/core/network/transports/ble';
package/types/index.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * @property {number} [recoveryTimeout] Max wait for peer data + writer capability during recovery.
20
20
  * @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.
21
21
  * @property {boolean} [extensions] `false` disables the bundled extensions (profileSync, handleSync) for this instance. Build with `{ extensions: false }` too so the spec matches.
22
- * @property {boolean | any} [bluetooth] `true` enables nearby (Bluetooth) sync via `me.bluetooth`; pass a bare-bluetooth-shaped backend to inject one. Absent backend → `me.bluetooth.state === 'unsupported'`.
22
+ * @property {boolean | { autoStart?: boolean, backend?: any }} [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); absent backend on an unsupported host → `me.bluetooth.state === 'unsupported'`.
23
23
  */
24
24
  /**
25
25
  * Open (or create) a cero handle at `dir`. Sets up storage, network and
@@ -133,9 +133,12 @@ export type CeroOpts = {
133
133
  */
134
134
  extensions?: boolean;
135
135
  /**
136
- * `true` enables nearby (Bluetooth) sync via `me.bluetooth`; pass a bare-bluetooth-shaped backend to inject one. Absent backend → `me.bluetooth.state === 'unsupported'`.
136
+ * `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); absent backend on an unsupported host → `me.bluetooth.state === 'unsupported'`.
137
137
  */
138
- bluetooth?: boolean | any;
138
+ bluetooth?: boolean | {
139
+ autoStart?: boolean;
140
+ backend?: any;
141
+ };
139
142
  };
140
143
  import { t } from './lib/spec.js';
141
144
  import { put } from './lib/operators.js';