44096069a7
- 发送端流式(R-iOS-4):新增 FileSource 抽象(web/src/features/transfer/source.ts),iOS 经 HTTP Range 惰性拉取(原生 WKURLSchemeHandler 认 Range 头 seek 回 206),整文件不进 WebView 内存;web/桌面经 fileSource 包装字节不变。p2p/relay/transfer 改吃 FileSource - 后台续传:iOS 26 BGContinuedProcessingTask(BackgroundTaskManager + AppDelegate 注册 + BGTaskSchedulerPermittedIdentifiers),引擎传输进度驱动系统进度 UI - APNs:后端 internal/apns(ES256 .p8 JWT + HTTP/2,仿 internal/push)+ push_subscriptions.platform 列 + POST /api/push/apns/register + transfer/message 未投递分支分流;原生 PushRegistry 授权 + 设备令牌经引擎桥 registerPush 上报;aps-environment + remote-notification 后台模式 - Share Extension:CDropShare appex 抓文件 → App Group 收件箱 → cdrop://share 深链唤主程序选设备发送(只交接、不跑引擎) - 控制中心剪贴板两控件:CDropWidgets widgetkit appex(ControlWidget + AppIntent + 纯原生 ClipboardClient REST);用专用 broker 设备会话(主程序经引擎 provisionWidgetSession 代铸独立 device_id 会话存 App Group,控件自刷不与引擎抢 refresh 轮换) - 真机分发预备:committed 签名改 ad-hoc 使模拟器 entitlement 生效;真机手动签名 scaffold(Signing.xcconfig 仅 device SDK 套 Manual + gitignore 的 Local.xcconfig 填 Team/profile)+ just ios-device / ios-devices 全 CLI 装机;包名改 net.commilitia.Commilitia-Drop(含 .share/.widgets,BG task 前缀同改);REALDEVICE.md 重写为门户 + CLI 装机手册 - I18n.swift 移 Shared/ 供三 target 共用;i18n 加 ios.bg/share/control.* 键 - ultracode 多 agent 审查修复(0 HIGH,2 MED + 7 LOW):分享 send 后留收件箱致重发→move 私有暂存;WidgetSession 锁屏文件保护→UntilFirstUserAuthentication;outgoing/安全作用域登出释放;控件 device_id 登出清理;并发控件 refresh CAS-before-clear;APNs 读 reason + prune bad token + bust 过期 JWT;APNSEnv 大小写归一;Share-ext completeRequest 挪进 open 回调
107 lines
4.1 KiB
Swift
107 lines
4.1 KiB
Swift
import BackgroundTasks
|
||
import UIKit
|
||
|
||
// 后台续传(iOS 26 BGContinuedProcessingTask)。前台发起的传输切到后台时,向系统申请续跑
|
||
// 任务并展示系统进度 UI(类比 AirDrop)。任务生命周期绑定活跃传输集合:0→>0(且前台)时
|
||
// 提交、随字节更新 progress、回到 0 时完成。
|
||
//
|
||
// 真机验证点(R-iOS-3):BGContinuedProcessingTask 给「应用」后台运行时间 + 系统进度 UI,
|
||
// 但承载传输的 WKWebView 是独立 WebContent 进程,其 JS / WebRTC 是否随之保活须真机验;若仍
|
||
// 被挂起,行为退化为「切后台暂停、回前台续传」(R-iOS-1 允许,文件传输本需前台)。
|
||
//
|
||
// 全程主线程驱动:register 用 .main 队列、sync 由引擎主线程回调触发,故不引入 actor 隔离。
|
||
final class BackgroundTaskManager
|
||
{
|
||
static let shared = BackgroundTaskManager()
|
||
private init() {}
|
||
|
||
// 标识符通配前缀。Info.plist 的 BGTaskSchedulerPermittedIdentifiers 须含 "<prefix>.*",
|
||
// 否则注册返回 false、提交抛错——届时仅退化为不申请后台续跑,传输逻辑不受影响。
|
||
private static let prefix = "net.commilitia.Commilitia-Drop.transfer"
|
||
private static let wildcard = prefix + ".*"
|
||
|
||
private var triedRegister = false
|
||
private var registerOK = false
|
||
private var task: BGContinuedProcessingTask?
|
||
private var pendingId: String? // 已提交、尚未 attach 的请求标识符(用于回到 0 时取消)。
|
||
private var latestFraction: Double = 0
|
||
|
||
// 应用启动时注册一次(须在 launch 完成前——由 AppDelegate.didFinishLaunching 调用)。
|
||
func registerOnce()
|
||
{
|
||
guard !triedRegister else { return }
|
||
triedRegister = true
|
||
registerOK = BGTaskScheduler.shared.register(
|
||
forTaskWithIdentifier: Self.wildcard, using: .main)
|
||
{ [weak self] task in
|
||
guard let cpt = task as? BGContinuedProcessingTask
|
||
else { task.setTaskCompleted(success: false); return }
|
||
self?.attach(cpt)
|
||
}
|
||
}
|
||
|
||
// 引擎每次刷新活跃传输时调用:activeCount 活跃传输数,fraction 总进度 [0, 1]。
|
||
func sync(activeCount: Int, fraction: Double)
|
||
{
|
||
latestFraction = fraction
|
||
if activeCount > 0
|
||
{
|
||
if let task { task.progress.completedUnitCount = Int64(fraction * 100) }
|
||
else { submitIfForeground() }
|
||
}
|
||
else
|
||
{
|
||
finish(success: true)
|
||
}
|
||
}
|
||
|
||
private func submitIfForeground()
|
||
{
|
||
// 注册失败(未声明权限)/ 已在运行 / 已提交待 attach:均不再提交。
|
||
guard registerOK, task == nil, pendingId == nil else { return }
|
||
// 系统拒绝后台提交(且传输入口本就在前台),故仅前台提交。
|
||
guard UIApplication.shared.applicationState != .background else { return }
|
||
|
||
let id = "\(Self.prefix).\(UUID().uuidString.prefix(8))"
|
||
let request = BGContinuedProcessingTaskRequest(
|
||
identifier: id, title: t("ios.bg.title"), subtitle: t("ios.bg.subtitle"))
|
||
request.strategy = .queue
|
||
do
|
||
{
|
||
try BGTaskScheduler.shared.submit(request)
|
||
pendingId = id
|
||
}
|
||
catch
|
||
{
|
||
// 提交失败(无空位 / 系统负载高):传输仍在前台正常跑,非致命。
|
||
}
|
||
}
|
||
|
||
private func attach(_ task: BGContinuedProcessingTask)
|
||
{
|
||
pendingId = nil
|
||
self.task = task
|
||
task.progress.totalUnitCount = 100
|
||
task.progress.completedUnitCount = Int64(latestFraction * 100)
|
||
task.expirationHandler = { [weak self] in
|
||
self?.task?.setTaskCompleted(success: false)
|
||
self?.task = nil
|
||
}
|
||
}
|
||
|
||
private func finish(success: Bool)
|
||
{
|
||
if let task
|
||
{
|
||
task.progress.completedUnitCount = task.progress.totalUnitCount
|
||
task.setTaskCompleted(success: success)
|
||
self.task = nil
|
||
}
|
||
if let pendingId
|
||
{
|
||
BGTaskScheduler.shared.cancel(taskRequestWithIdentifier: pendingId)
|
||
self.pendingId = nil
|
||
}
|
||
}
|
||
}
|