Files
BeamScribe/IMPLEMENTATION_PLAN_NETWORK_FRAMEWORK.md
jared 0c1e3d6fff Refactor networking to use Network.framework
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.
2026-01-20 23:48:14 -05:00

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
```