@cero-base/cero 1.2.0 → 1.3.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.3.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.3.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
@@ -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'
@@ -114,9 +115,18 @@ 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
132
  this._transport = new BluetoothTransport({
@@ -127,7 +137,8 @@ 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 || ''
131
142
  })
132
143
  this._transport.on('update', () => {
133
144
  this.state = this._transport.state
@@ -156,17 +167,24 @@ export class Bluetooth extends ReadyResource {
156
167
  await t.close().catch(safetyCatch)
157
168
  }
158
169
  if (!this._transport) return
159
- try {
160
- await this._transport.close()
161
- } catch (err) {
162
- safetyCatch(err)
163
- }
164
- this._transport = null
170
+ // suspend, don't close: keep the transport (and its GATT service) so a later
171
+ // start() resumes the same instance instead of leaking a new one. suspend()
172
+ // is async (it says goodbye + drains before hanging up); await it.
173
+ await this._transport.suspend()
165
174
  this.state = 'off'
166
175
  this.emit('update')
167
176
  }
168
177
 
169
178
  async _close() {
170
- await this.stop()
179
+ for (const t of [...this._announces]) {
180
+ this._announces.delete(t)
181
+ await t.close().catch(safetyCatch)
182
+ }
183
+ if (this._transport) {
184
+ await this._transport.close().catch(safetyCatch)
185
+ this._transport = null
186
+ }
187
+ this.state = 'off'
188
+ this.emit('update')
171
189
  }
172
190
  }
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()
@@ -28,6 +28,7 @@ export class Bluetooth extends ReadyResource {
28
28
  _backend: any;
29
29
  _autoStart: boolean;
30
30
  _transport: BluetoothTransport;
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.
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';