Files
photo-live/apps/mobile/ios/Runner/UploadBridge.swift
T

108 lines
3.9 KiB
Swift
Raw Normal View History

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<String, String>")
}
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
)
}