import Foundation public struct RosterPeer: Decodable, Identifiable { public let peer: String public let name: String public let kind: String public let gen: Int64? /// The peer's own last report of its microphone and camera (spec /// §15.19). Nil is unknown — a client that predates the report, or one /// that has not made it yet — and is shown as neither muted nor off. public let media: PeerMedia? public var id: String { peer } } /// What a peer says about its own outgoing media (spec §15.19), and about /// the sound reaching it (§15.20). public struct PeerMedia: Decodable, Equatable, Sendable { public let muted: Bool public let cameraOff: Bool /// Its browser refused to play sound and is waiting for a click. Nil /// (absent) is none; the key only travels with bad news. public let audioBlocked: Bool? /// The peers whose audio reaches it as silence. Nil (absent) is none. public let noSoundFrom: [String]? /// The peers whose shared screen has stopped moving for it (§15.21). /// A sharer that finds itself here recovers its screen. Nil is none. public let screenStalled: [String]? /// The peers whose audio it is actually receiving and playing (§15.24). /// Nil (absent) is a client that does not say; empty is one that hears /// nobody yet. public let hearing: [String]? public init( muted: Bool, cameraOff: Bool, audioBlocked: Bool? = nil, noSoundFrom: [String]? = nil, screenStalled: [String]? = nil, hearing: [String]? = nil ) { self.muted = muted self.cameraOff = cameraOff self.audioBlocked = audioBlocked self.noSoundFrom = noSoundFrom self.screenStalled = screenStalled self.hearing = hearing } } public struct RoomState: Decodable { public let phase: String public let peer: String public let boot: String public let reason: String? public let peers: [RosterPeer] public let waiting: [RosterPeer] } struct WireEvent: Decodable { let type: String let state: RoomState? let message: Message? let ice: ICEConfig? let info: MeetingInfo? let signal: Message? } struct Message: Decodable { let type: String let from: String? let peer: String? let name: String? let gen: Int64? let seq: UInt64? let payload: Payload? } struct Payload: Decodable { let description: Description? let candidate: Candidate? let text: String? /// `share`'s one field (§5.4): whether the sender is showing a screen. let on: Bool? } struct Description: Codable { let type: String let sdp: String } struct Candidate: Codable { let candidate: String let sdpMid: String? let sdpMLineIndex: Int32? } struct ICEConfig: Decodable { struct Server: Decodable { let urls: [String] let username: String? let credential: String? } let iceServers: [Server] let renewAt: Int64 let expiresAt: Int64 } /// §5.3 for the apps: when a fresh `/ice` answer should restart ICE on a /// connection, rather than be adopted for whatever it gathers next. /// /// The core fetches the list on every roster (every stream resume, five /// minutes apart at most) as well as at `renewAt`, so a connection hears a /// fresh list at the latest five minutes after its own renewal falls due — /// well inside the half hour between `renewAt` and expiry. enum ICERenewal { /// - Parameters: /// - gathered: the list the connection was built with, or last restarted onto. /// - next: the list just fetched. /// - now: unix seconds. static func restart(gathered: ICEConfig?, next: ICEConfig, now: Int64) -> Bool { // The floor has nothing to renew onto. guard !next.iceServers.isEmpty else { return false } guard let gathered else { return true } // Built on the floor while the first fetch was out, and a relay since: // adopt it now, as the browser's adoptServers does. if gathered.iceServers.isEmpty { return true } return gathered.renewAt > 0 && now >= gathered.renewAt } } func json(_ value: Any) -> String? { guard JSONSerialization.isValidJSONObject(value), let data = try? JSONSerialization.data(withJSONObject: value) else { return nil } return String(data: data, encoding: .utf8) } struct MeetingInfo: Decodable { let title: String /// A title someone chose rather than one Calls wrote. Absent from a /// server before 0.4.7, where it reads as false and no title is shown. let named: Bool? let name: String let standing: String let floor: Bool }