import MultipeerConnectivity import Foundation import Combine struct OwnPeer { let peer: MCPeerID static var fallback: Self { Self(peer: .init(displayName: "fallback_user")) } } struct Peer: Identifiable { enum ConnectionState { case available case joined case rejected case invitationPending } var id: String { mcPeer.displayName } let mcPeer: MCPeerID var state: ConnectionState } @Observable final class NoteEditingSessionServer: NSObject { private let session: MCSession private let browser: MCNearbyServiceBrowser private let ownPeer: OwnPeer var visiblePeers: [Peer] = [] let noteChangesEmitter = PassthroughSubject() init(peer: OwnPeer) { ownPeer = peer browser = .init(peer: peer.peer, serviceType: "peered") session = .init(peer: peer.peer) super.init() browser.delegate = self session.delegate = self } func startServer() { browser.startBrowsingForPeers() } func stopServer() { browser.stopBrowsingForPeers() } func invite(peer: Peer, to note: NoteInvitation.NoteContent) { guard peer.state == .available else { return } browser.invitePeer( peer.mcPeer, to: session, withContext: try! JSONEncoder().encode(note), timeout: 5 ) let idxToUpdate = visiblePeers.firstIndex(where: { $0.mcPeer == peer.mcPeer })! visiblePeers[idxToUpdate].state = .invitationPending } } extension NoteEditingSessionServer: MCNearbyServiceBrowserDelegate { func browser( _ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]? ) { guard !visiblePeers.contains(where: { $0.mcPeer == peerID }) && peerID.displayName != ownPeer.peer.displayName else { return } let newPeer = Peer(mcPeer: peerID, state: .available) visiblePeers.append(newPeer) } func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { guard let peerIdx = visiblePeers.firstIndex(where: { $0.mcPeer == peerID }) else { return } visiblePeers.remove(at: peerIdx) } } extension NoteEditingSessionServer: MCSessionDelegate { func session( _ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState ) { } func session( _ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID ) { } func session( _ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress ) { } func session( _ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: (any Error)? ) { } func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) { guard let note = String(data: data, encoding: .utf8) else { fatalError() } noteChangesEmitter.send(note) } }