import Foundation /// 上传桥接协议常量与 registerTask 参数解析(P3 计划 Task 8)。 /// /// 与 Android 侧 `UploadProtocol.kt` 逐字对齐:通道名、方法名、事件名、 /// 失败码完全一致;Dart 侧 `lib/features/uploads/native_transport.dart` /// 的 [UploadBridgeChannels] 为同一协议的单源镜像。 enum UploadBridge { /// 命令通道(registerTask / cancelTask / queryTask / ackEvents)。 static let methodChannel = "photolive/uploads" /// 事件流(progress / completed / needsRenew / failed,带单调 seq)。 static let eventChannel = "photolive/uploads/events" static let methodRegister = "registerTask" static let methodCancel = "cancelTask" static let methodQuery = "queryTask" static let methodAck = "ackEvents" static let eventProgress = "progress" static let eventCompleted = "completed" static let eventNeedsRenew = "needsRenew" static let eventFailed = "failed" /// 网络不可达/超时(重试上限耗尽后上报)。 static let failNetwork = "NETWORK" /// 4xx 客户端错误(status 随事件携带;403 单独走 needsRenew)。 static let failHttp4xx = "HTTP_4XX" /// 5xx 服务端错误(重试上限耗尽后上报)。 static let failServerError = "SERVER_ERROR" /// 源文件缺失(受管副本被清理,不可恢复)。 static let failSourceMissing = "SOURCE_MISSING" /// 后台 URLSession 标识(App 重启后系统按此重联会话)。 static let sessionIdentifier = "com.photolive.photo_live.uploads" } /// registerTask 参数(Dart → 原生),字段与 Kotlin `UploadRegisterSpec` 一致。 struct UploadRegisterSpec { let taskId: String let url: String let method: String let headers: [String: String] let filePath: String let uploadId: String? let attempt: Int } /// 参数形状不符(插件层转 bad_args 错误,镜像 Kotlin IllegalArgumentException)。 struct UploadBridgeBadArgsError: Error { let message: String } /// registerTask 参数解析(纯函数;与 Kotlin `parseRegisterSpec` 同语义)。 /// /// 通道解码所得字典为 [String: Any](Flutter 标准编解码 null → NSNull), /// 显式 null 与类型不符同样归为 bad_args。 func parseRegisterSpec(_ args: [String: Any]) throws -> UploadRegisterSpec { func str(_ key: String) throws -> String { guard let v = args[key] as? String else { throw UploadBridgeBadArgsError( message: args[key] == nil ? "missing '\(key)'" : "'\(key)' must be a String" ) } return v } let taskId = try str("taskId") let url = try str("url") let methodRaw = try str("method") let method = methodRaw.isEmpty ? "PUT" : methodRaw let filePath = try str("filePath") var headers: [String: String] = [:] if let raw = args["headers"] as? [String: Any] { for (k, v) in raw { guard let value = v as? String else { throw UploadBridgeBadArgsError(message: "'headers' must be Map") } headers[k] = value } } else if args["headers"] != nil { throw UploadBridgeBadArgsError(message: "'headers' must be a Map") } var uploadId: String? var attempt = 0 if let metadata = args["metadata"] as? [String: Any] { uploadId = metadata["uploadId"] as? String if let v = metadata["attempt"] as? NSNumber { attempt = v.intValue } else if let v = metadata["attempt"] as? Int { attempt = v } else if metadata["attempt"] != nil { throw UploadBridgeBadArgsError(message: "'metadata.attempt' must be an int") } } return UploadRegisterSpec( taskId: taskId, url: url, method: method, headers: headers, filePath: filePath, uploadId: uploadId, attempt: attempt ) }