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 回调
78 lines
2.9 KiB
Swift
78 lines
2.9 KiB
Swift
import UIKit
|
||
import UniformTypeIdentifiers
|
||
|
||
// Share Extension 主控制器:把 share sheet 传入的文件拷进 App Group 收件箱,再深链唤起主 app
|
||
// 选设备发送(扩展只交接、不跑引擎,避 120MB jetsam,见 PLAN §I5)。无 compose UI——即起即办、
|
||
// 处理完即返回。
|
||
final class ShareViewController: UIViewController
|
||
{
|
||
override func viewDidAppear(_ animated: Bool)
|
||
{
|
||
super.viewDidAppear(animated)
|
||
Task { await processAndClose() }
|
||
}
|
||
|
||
private func processAndClose() async
|
||
{
|
||
let items = (extensionContext?.inputItems as? [NSExtensionItem]) ?? []
|
||
var staged = 0
|
||
for item in items
|
||
{
|
||
for provider in item.attachments ?? []
|
||
{
|
||
if await stage(provider) { staged += 1 }
|
||
}
|
||
}
|
||
let ctx = extensionContext
|
||
guard staged > 0, let url = URL(string: "cdrop://share") else
|
||
{
|
||
ctx?.completeRequest(returningItems: nil)
|
||
return
|
||
}
|
||
// 先唤起主 app,等 open 回调拿到结果再 completeRequest——否则立即 completeRequest 会拆掉
|
||
// 扩展、self 被释放,open 失败时的 openViaResponder 兜底永不触发(审查 L8)。捕获 ctx 使
|
||
// 扩展存活到回调。
|
||
ctx?.open(url)
|
||
{ [weak self] ok in
|
||
if !ok { self?.openViaResponder(url) }
|
||
ctx?.completeRequest(returningItems: nil)
|
||
}
|
||
}
|
||
|
||
// 把单个附件落到 App Group 收件箱。用 loadFileRepresentation 取系统给的临时文件表示
|
||
// (文件 / 图片 / 文档统一走此路,且大文件不整体进内存),在回调内同步拷到收件箱
|
||
// (系统会在回调返回后删临时文件)。
|
||
private func stage(_ provider: NSItemProvider) async -> Bool
|
||
{
|
||
return await withCheckedContinuation
|
||
{ cont in
|
||
provider.loadFileRepresentation(forTypeIdentifier: UTType.item.identifier)
|
||
{ url, _ in
|
||
guard let url, let dest = AppGroup.stagedDestination(originalName: url.lastPathComponent)
|
||
else { cont.resume(returning: false); return }
|
||
do
|
||
{
|
||
try FileManager.default.copyItem(at: url, to: dest)
|
||
cont.resume(returning: true)
|
||
}
|
||
catch
|
||
{
|
||
cont.resume(returning: false)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// open 失败时的兜底:沿响应链找 UIApplication 唤起主 app(个别系统版本扩展直接 open 不生效)。
|
||
private func openViaResponder(_ url: URL)
|
||
{
|
||
var responder: UIResponder? = self
|
||
let sel = NSSelectorFromString("openURL:")
|
||
while let r = responder
|
||
{
|
||
if r.responds(to: sel) { _ = r.perform(sel, with: url); return }
|
||
responder = r.next
|
||
}
|
||
}
|
||
}
|