import Foundation // P2PEventsBridge:libwebrtc 原生数据面引擎(LibWebRtcEngine)的事件宿主。引擎在 libwebrtc 信令 // 线程(非主线程)触发这些回调,每条都用 DispatchQueue.main.async 跳回主线程再交 EngineController // 过桥给无头 JS 引擎(evaluateJavaScript 仅限主线程)。 // // 用 DispatchQueue.main.async 而非 Task { @MainActor }:前者对同源提交保持 FIFO,使 // connected → completed 等有序状态不被乱序处理(Task 无顺序保证,completed 抢在 connected 前 // 处理会把终态回退)。assumeIsolated 把主队列闭包桥进 EngineController 的 @MainActor 隔离域。 // // 与桌面 desktop/app.go 的 transferEvents 等价——桌面经 Wails EventsEmit,iOS 经 cdropEngine 桥。 // 事件契约见 LibWebRtcEngine.swift 的 P2PEngineDelegate;JS 侧消费见 // web/src/features/transfer/p2pIos.ts(事件名 / payload 形状与桌面 NativeP2PHandlers 同形)。 final class P2PEventsBridge: P2PEngineDelegate { weak var controller: EngineController? // 已传字节(发送端=接收端 ack 追平、接收端=已写盘)。 func engineDidProgress(sessionId: String, bytes: Int64) { emit("p2pProgress", [ "sessionId": sessionId, "bytes": bytes ]) } // 会话状态:connected / completed / failed / closed。 func engineDidChangeState(sessionId: String, state: String) { emit("p2pState", [ "sessionId": sessionId, "state": state ]) } // 出站信令(offer/answer/ice):payloadJSON 为信令 JSON 串,JS 侧 POST /api/hub/signal 前 // 解析(payload 透传字符串再 JSON.parse,与桌面 p2pNative onSignal 同形)。 func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String) { emit("p2pSignal", [ "sessionId": sessionId, "to": toPeer, "payload": payloadJSON, ]) } // 接收端落盘完成的绝对路径(沙盒 Documents)。落盘提示由原生 transferDone 链路负责,JS 侧 // p2pIos onSaved 当前为 no-op;事件仍过桥以备用。 func engineDidSave(sessionId: String, path: String) { emit("p2pSaved", [ "sessionId": sessionId, "path": path ]) } // 选中候选对(形如 "host/udp"):填进传输记录的 iceStats 供调试面板展示实际连通路径。 func engineDidSelectIcePair(sessionId: String, local: String, remote: String) { emit("p2pIcePair", [ "sessionId": sessionId, "local": local, "remote": remote, ]) } // 引擎诊断日志(候选 / ICE 态 / 选中对 / 发送停滞):直接进原生日志环形缓冲(#3 日志界面), // 不过 JS——诊断由原生侧呈现,便于真机现场排查同内网 mDNS/ICE 时间线。 func engineDidLog(_ line: String) { let msg = line DispatchQueue.main.async { [weak controller] in MainActor.assumeIsolated { controller?.appendNativeLog(msg) } } } private func emit(_ name: String, _ payload: [String: Any]) { DispatchQueue.main.async { [weak controller] in MainActor.assumeIsolated { controller?.sendCommand(name, payload: payload) } } } }