@@ -189,34 +189,53 @@ class BleAdvertiserChannel: NSObject {
189189 /// Actually start the BLE advertisement. Called only after the GATT
190190 /// service has been confirmed registered via `didAdd`.
191191 ///
192- /// Includes the Field Link sessionId as 16 raw bytes of service data
193- /// keyed by the service UUID. Joiners parse this payload from
194- /// `result.advertisementData.serviceData` (flutter_blue_plus) and use
195- /// it as the canonical CRDT session id. Without this, joiners would
196- /// fall back to the BLE peripheral's CoreBluetooth UUID, mismatch the
197- /// host's UUID v4 sessionId, and silently lose all sync data.
192+ /// CRITICAL: Apple does NOT support `CBAdvertisementDataServiceDataKey`
193+ /// in `CBPeripheralManager.startAdvertising` for third-party apps —
194+ /// the documentation is misleading. Passing it causes a SIGABRT in
195+ /// Apple's XPC serialization with `-[CBUUID UTF8String]: unrecognized
196+ /// selector` (the inner `[CBUUID: Data]` dict is rejected because
197+ /// CoreBluetooth's XPC layer only accepts string keys for that
198+ /// payload). v1.5.4+307 had this and TestFlight crash logs caught it
199+ /// on iPad / iOS 26.3. Fixed in v1.5.4+308 by reverting to the
200+ /// Apple-blessed 2-key dict (ServiceUUIDs + optional LocalName).
198201 ///
199- /// NOTE: CoreBluetooth's foreground advertisement payload is limited
200- /// to 31 bytes. Service-UUID-128 takes 18 bytes; 16 bytes of service
201- /// data plus the 2-byte service-UUID prefix and 1-byte AD-type header
202- /// = 19 bytes, totalling 37. iOS handles the overflow by spilling the
203- /// service data into the scan-response packet, which flutter_blue_plus
204- /// merges back into `result.advertisementData.serviceData` on the
205- /// scanning side. The local name "RGL" was dropped to keep the primary
206- /// advertisement compact and to avoid further overflow.
202+ /// The session id is therefore NOT carried in the BLE advertisement
203+ /// from iOS hosts. iOS-to-iOS discovery falls back to Multipeer
204+ /// Connectivity (wired in main.dart via MultiTransport), which uses
205+ /// Bonjour's discoveryInfo to carry the session id. iOS hosts seen
206+ /// by Android scanners still appear, but `device.sessionId` is null,
207+ /// so the joiner UI prompts for QR / Manual entry.
207208 private func beginAdvertising( ) {
208209 guard let pm = peripheralManager else { return }
209210
210- var advertData : [ String : Any ] = [
211+ // Apple's whitelist for `startAdvertising:` is:
212+ // - CBAdvertisementDataServiceUUIDsKey (array of CBUUID)
213+ // - CBAdvertisementDataLocalNameKey (String)
214+ // Anything else either silently strips OR crashes the XPC layer.
215+ let advertData : [ String : Any ] = [
211216 CBAdvertisementDataServiceUUIDsKey: [ BleAdvertiserChannel . serviceUUID] ,
212217 ]
213218
214- if let sessionId = pendingSessionId,
215- let sessionBytes = BleAdvertiserChannel . sessionIdToBytes ( sessionId) {
216- // CBAdvertisementDataServiceDataKey is a [CBUUID: Data] dict.
217- advertData [ CBAdvertisementDataServiceDataKey] = [
218- BleAdvertiserChannel . serviceUUID: Data ( sessionBytes) ,
219- ]
219+ // Belt-and-braces guard: if a future change re-introduces a key
220+ // outside the whitelist (and especially one whose VALUE contains
221+ // a non-NSString-keyed dict, like CBAdvertisementDataServiceDataKey
222+ // = [CBUUID: Data]), the XPC serialization in CoreBluetooth will
223+ // SIGABRT inside `pm.startAdvertising`. Fail loudly during dev/
224+ // beta builds so the regression is caught before another TestFlight
225+ // crash report.
226+ let allowedKeys : Set < String > = [
227+ CBAdvertisementDataServiceUUIDsKey,
228+ CBAdvertisementDataLocalNameKey,
229+ ]
230+ for k in advertData. keys {
231+ assert (
232+ allowedKeys. contains ( k) ,
233+ """
234+ BleAdvertiserChannel: advertData contains key ' \( k) ' which
235+ is NOT in Apple's documented whitelist for startAdvertising:.
236+ See v1.5.4+307 TestFlight crash (CBUUID UTF8String).
237+ """
238+ )
220239 }
221240
222241 pm. startAdvertising ( advertData)
0 commit comments