Replace MultipeerConnectivity with a custom P2P implementation using Network.framework (NWListener/NWConnection) and CoreBluetooth for discovery. - Add P2PConnectionManager, BLEDiscoveryManager, and NetworkFraming. - Add ConnectedPeer and DiscoveredHost models. - Update Info.plist with local network and bluetooth permissions. - Refactor Views to use P2PConnectionManager. - Add implementation plan and transition docs.
244 lines
7.1 KiB
Markdown
244 lines
7.1 KiB
Markdown
# BeamScribe: MPC to Network.framework Transition Plan
|
|
|
|
## Overview
|
|
Transition from MultipeerConnectivity to Network.framework while preserving all existing functionality: live transcription broadcast, late-joiner history sync, alerts, and multi-guest support.
|
|
|
|
---
|
|
|
|
## Phase 1: Create P2PConnectionManager (Network.framework Core)
|
|
|
|
### 1.1 Define the Protocol Interface
|
|
Create a protocol that mirrors what `MultipeerManager` currently provides to the UI:
|
|
|
|
```swift
|
|
protocol P2PConnectionDelegate: AnyObject {
|
|
func didDiscoverHost(_ host: DiscoveredHost)
|
|
func didLoseHost(_ host: DiscoveredHost)
|
|
func didConnectGuest(_ guest: ConnectedPeer)
|
|
func didDisconnectGuest(_ guest: ConnectedPeer)
|
|
func didReceiveData(_ data: Data, from peer: ConnectedPeer)
|
|
func connectionStateChanged(_ state: P2PConnectionState)
|
|
}
|
|
```
|
|
|
|
### 1.2 Host Side: NWListener
|
|
Replace `MCNearbyServiceAdvertiser` with `NWListener`:
|
|
|
|
```swift
|
|
// Key configuration
|
|
let parameters = NWParameters.tcp
|
|
parameters.includePeerToPeer = true // Enables AWDL
|
|
parameters.serviceClass = .responsiveData // Low-latency priority
|
|
|
|
// Advertise with service type
|
|
let listener = try NWListener(using: parameters)
|
|
listener.service = NWListener.Service(
|
|
name: eventName, // Your event name (currently in discoveryInfo)
|
|
type: "_beamscribe._tcp"
|
|
)
|
|
```
|
|
|
|
**Connection Handling:**
|
|
- Store accepted connections in `[UUID: NWConnection]` dictionary
|
|
- Implement same 4-second "traffic gate" stabilization logic
|
|
- Track unstable connections (disconnect within 5 seconds)
|
|
|
|
### 1.3 Guest Side: NWBrowser + NWConnection
|
|
Replace `MCNearbyServiceBrowser` with `NWBrowser`:
|
|
|
|
```swift
|
|
let parameters = NWParameters.tcp
|
|
parameters.includePeerToPeer = true
|
|
|
|
let browser = NWBrowser(for: .bonjour(type: "_beamscribe._tcp", domain: nil), using: parameters)
|
|
```
|
|
|
|
**Discovery Results:**
|
|
- `NWBrowser.Result` includes the service name (your event name)
|
|
- Create `NWConnection` to connect to discovered host
|
|
- Implement retry logic (1s, 2s, 4s backoff - same as current)
|
|
|
|
---
|
|
|
|
## Phase 2: BLE Fast-Discovery Layer (CoreBluetooth)
|
|
|
|
### 2.1 Why BLE?
|
|
Standard Bonjour discovery over AWDL can take 2-8 seconds due to channel hopping. BLE advertisement is near-instant and "wakes" the AWDL interface.
|
|
|
|
### 2.2 BLEDiscoveryManager - Host (Peripheral)
|
|
|
|
```swift
|
|
let serviceUUID = CBUUID(string: "YOUR-BEAMSCRIBE-UUID")
|
|
|
|
// Advertise when hosting
|
|
peripheralManager.startAdvertising([
|
|
CBAdvertisementDataServiceUUIDsKey: [serviceUUID],
|
|
CBAdvertisementDataLocalNameKey: eventName.prefix(8) // BLE has 28-byte limit
|
|
])
|
|
```
|
|
|
|
### 2.3 BLEDiscoveryManager - Guest (Central)
|
|
|
|
```swift
|
|
// Scan for BeamScribe hosts
|
|
centralManager.scanForPeripherals(withServices: [serviceUUID])
|
|
|
|
// On discovery, trigger Network.framework browser
|
|
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, ...) {
|
|
delegate?.bleDidDiscoverHost(name: peripheral.name)
|
|
// Start NWBrowser immediately - AWDL is now primed
|
|
}
|
|
```
|
|
|
|
### 2.4 BLE Lifecycle
|
|
- Start BLE advertising when `startHosting()` called
|
|
- Start BLE scanning when `startBrowsing()` called
|
|
- Stop BLE once TCP connection established (saves battery)
|
|
- Resume BLE if connection lost (for reconnection)
|
|
|
|
---
|
|
|
|
## Phase 3: Data Transmission Layer
|
|
|
|
### 3.1 Framing Protocol
|
|
Network.framework uses raw TCP streams. Implement length-prefixed framing:
|
|
|
|
```swift
|
|
// Sending
|
|
func send(_ packet: TranscriptPacket, to connection: NWConnection) {
|
|
let data = try JSONEncoder().encode(packet)
|
|
var length = UInt32(data.count).bigEndian
|
|
let header = Data(bytes: &length, count: 4)
|
|
connection.send(content: header + data, completion: .contentProcessed { ... })
|
|
}
|
|
|
|
// Receiving
|
|
func receiveNextPacket(on connection: NWConnection) {
|
|
// Read 4-byte header first, then read that many bytes for payload
|
|
}
|
|
```
|
|
|
|
### 3.2 Broadcast to All Guests (Host)
|
|
Replace `MCSession.send(_:toPeers:with:)`:
|
|
|
|
```swift
|
|
func broadcastPacket(_ packet: TranscriptPacket) {
|
|
let data = encode(packet)
|
|
for (_, connection) in stableConnections {
|
|
connection.send(content: data, completion: ...)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.3 Packet Types (Keep Existing)
|
|
Your current `TranscriptPacket` model works unchanged:
|
|
- `.fullHistory` - Late-joiner sync
|
|
- `.liveChunk` - Real-time transcription (partial/final)
|
|
- `.alert` - Host disconnected, battery low, session resumed
|
|
|
|
---
|
|
|
|
## Phase 4: Integration with Existing Code
|
|
|
|
### 4.1 File Changes Summary
|
|
|
|
| Current File | Changes |
|
|
|--------------|---------|
|
|
| `MultipeerManager.swift` | Deprecate, keep as fallback initially |
|
|
| `P2PConnectionManager.swift` | NEW - Main networking logic |
|
|
| `BLEDiscoveryManager.swift` | NEW - CoreBluetooth layer |
|
|
| `NetworkFraming.swift` | NEW - TCP framing utilities |
|
|
| `SessionState.swift` | Update peer tracking types |
|
|
| `ContentView.swift` | Swap manager reference |
|
|
| `GuestBrowserView.swift` | Use new discovery delegate |
|
|
| `Info.plist` | Add BLE background modes |
|
|
|
|
### 4.2 Info.plist Additions
|
|
```xml
|
|
<key>UIBackgroundModes</key>
|
|
<array>
|
|
<string>bluetooth-central</string>
|
|
<string>bluetooth-peripheral</string>
|
|
</array>
|
|
<key>NSBluetoothAlwaysUsageDescription</key>
|
|
<string>BeamScribe uses Bluetooth to quickly discover nearby sessions.</string>
|
|
```
|
|
|
|
### 4.3 Fallback Strategy
|
|
Keep `MultipeerManager` available initially:
|
|
```swift
|
|
// In SessionState or AppConfig
|
|
var useNetworkFramework: Bool = true
|
|
|
|
var connectionManager: any P2PConnectionDelegate {
|
|
useNetworkFramework ? p2pManager : legacyMultipeerManager
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 5: Testing & Verification
|
|
|
|
### 5.1 Terminal Checks
|
|
```bash
|
|
# Verify AWDL activates during connection
|
|
ifconfig awdl0
|
|
|
|
# Check for active peer-to-peer interface
|
|
netstat -rn | grep awdl
|
|
```
|
|
|
|
### 5.2 Latency Testing
|
|
```swift
|
|
// Add to packet for round-trip measurement
|
|
struct TranscriptPacket {
|
|
// existing fields...
|
|
var sentTimestamp: TimeInterval?
|
|
}
|
|
```
|
|
|
|
### 5.3 Test Scenarios
|
|
- [ ] Host starts, 1 guest joins
|
|
- [ ] Host starts, 7 guests join simultaneously
|
|
- [ ] Guest joins late, receives full history
|
|
- [ ] Host disconnects, guests receive alert
|
|
- [ ] App backgrounded, BLE keeps discovery alive
|
|
- [ ] Same Wi-Fi network (should prefer infrastructure)
|
|
- [ ] Different Wi-Fi / no Wi-Fi (should use AWDL)
|
|
|
|
---
|
|
|
|
## Implementation Order
|
|
|
|
1. **P2PConnectionManager** - NWListener + NWBrowser (no BLE yet)
|
|
2. **NetworkFraming** - Length-prefixed TCP framing
|
|
3. **Integration** - Wire up to ContentView/GuestBrowserView
|
|
4. **Testing** - Verify feature parity with MPC
|
|
5. **BLEDiscoveryManager** - Add fast-discovery layer
|
|
6. **Optimization** - Infrastructure vs AWDL preference logic
|
|
7. **Cleanup** - Remove MultipeerManager fallback
|
|
|
|
---
|
|
|
|
## Estimated New Files
|
|
|
|
```
|
|
BeamScribe/
|
|
├── Managers/
|
|
│ ├── MultipeerManager.swift (existing - deprecate later)
|
|
│ ├── P2PConnectionManager.swift (NEW - ~400 lines)
|
|
│ ├── BLEDiscoveryManager.swift (NEW - ~150 lines)
|
|
│ └── NetworkFraming.swift (NEW - ~80 lines)
|
|
├── Models/
|
|
│ ├── DiscoveredHost.swift (NEW - ~20 lines)
|
|
│ └── ConnectedPeer.swift (NEW - ~20 lines)
|
|
```
|
|
|
|
---
|
|
|
|
## Rollback Plan
|
|
If issues arise:
|
|
```bash
|
|
git reset --hard backup-before-networkframework
|
|
```
|