传输等待前台:后台 / 墓碑 iOS 可作可唤醒传输目标 + 发送端等其回前台再传
- 根因:presence 的 online 仅表示有活 SSE,故后台 / 墓碑态 iOS 被判离线、不进传输目标,使前面的后台响应(推送唤醒 + 离线队列)实际无从触发。诉求模型:离线但可唤醒的设备可选作目标;发送端推送唤醒对端、等其回前台上线后再走实时传输(非服务端暂存转发)。 - 后端:transfer.Init 返回 receiverOnline(=SendTo delivered);handleTransferInit 回 receiver_online。新增 ListPendingTransfersByReceiver 查询 + Service.RedeliverPendingTo——接收端重连即补发其仍 PENDING 的 transfer:incoming(窗口内),使被唤醒的 iOS 重新 arm P2P answerer(否则发送端 offer 因 p2pHandleSignal 不识未知 peer 而被丢);sse.go handleEvents 在 hub.Connect 后调用它。 - 发送端 web/transfer.ts:startOutgoingTransfer 按 receiver_online 分流——在线即 beginP2P(原 P2P + fallback 逻辑抽出);离线则 waitForPeerThenBegin:标 phase=waiting_peer、订阅 presence、对端上线即延 1.2s(待其 arm)再 beginP2P;60s 窗口内不上线则 cancel + FAILED phase=peer_offline。cleanupOutgoing 清理 waitTimer / waitUnsub。TransferPhase 加 waiting_peer / peer_offline + 三语 i18n。 - 目标纳入 + 标记:离线但可唤醒(type 含 ios)的设备可选作传输目标、标「可唤醒」。web DeviceStrip 加 isWakeable(纳入 visible、放开 disabled、标签);iOS EngineController.sendableDevices 纳入 + isWakeable + sendLabel,三处发送选择器(传输 / 分享 / 收到的文件转发)改用 sendLabel。i18n 加 home.device.wakeable。 - 机制说明(留档):content-available 静默唤醒不会让挂起的引擎重连 SSE;真正上线=用户点推送回前台 → 引擎恢复 → SSE 重连 → 后端补发 incoming → arm → 发送端 offer。若 offer 与 arm 抢拍掉了,30s 后 relay 兜底(此时对端已在线)。可唤醒判定用类型启发式(离线 + ios),未加后端 push-sub 查询。 - 测试:service_test 适配 Init 的三返回值。
This commit is contained in:
@@ -334,10 +334,25 @@ final class EngineController: NSObject
|
||||
return files.first { $0.lastPathComponent.hasPrefix(base) }
|
||||
}
|
||||
|
||||
// 可发送目标:在线、且非本机(不发给自己)。发送 / 转发两处入口共用此过滤。
|
||||
// 可发送目标:在线设备 + 离线但可唤醒(iOS,有 APNs)的设备,且非本机。给离线可唤醒设备发送时,
|
||||
// 后端推送唤醒它、发送端等其回前台 / 上线后再发起实时传输(见 web transfer.ts waitForPeerThenBegin)。
|
||||
func sendableDevices() -> [DeviceItem]
|
||||
{
|
||||
return devices.filter { $0.online && $0.name != deviceName }
|
||||
return devices.filter { $0.name != deviceName && ($0.online || Self.isWakeable($0)) }
|
||||
}
|
||||
|
||||
// 离线但可经推送唤醒(目前=iOS App):可作为传输目标。
|
||||
static func isWakeable(_ dev: DeviceItem) -> Bool
|
||||
{
|
||||
if dev.online { return false }
|
||||
let k = dev.type.lowercased()
|
||||
return k.contains("ios") || k.contains("iphone") || k.contains("ipad")
|
||||
}
|
||||
|
||||
// 发送选择器里的设备标签:可唤醒(离线)设备附「可唤醒」提示,区别于在线设备。
|
||||
func sendLabel(_ dev: DeviceItem) -> String
|
||||
{
|
||||
return Self.isWakeable(dev) ? "\(dev.name) · \(t("home.device.wakeable"))" : dev.name
|
||||
}
|
||||
|
||||
// 剪贴板上行:读本机 UIPasteboard 文本交引擎上传云剪贴板(用户手势触发,前台读取合规)。
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"nav.themeToggle": "Toggle theme",
|
||||
"home.greeting": "Hello, {{name}}",
|
||||
"home.deviceList.title": "Devices",
|
||||
"home.device.wakeable": "Wakeable",
|
||||
"home.deviceList.onlineSuffix": "({{count}} online)",
|
||||
"home.deviceList.showOffline": "Show offline ({{count}})",
|
||||
"home.deviceList.hideOffline": "Hide offline",
|
||||
@@ -256,6 +257,8 @@
|
||||
"transfer.mode.relay": "Relay",
|
||||
"transfer.phase.initializing": "Initializing…",
|
||||
"transfer.phase.waiting_accept": "Waiting for the other side to accept…",
|
||||
"transfer.phase.waiting_peer": "Waiting for the device to come online…",
|
||||
"transfer.phase.peer_offline": "Device didn't come online",
|
||||
"transfer.phase.ice_gathering": "Gathering network candidates…",
|
||||
"transfer.phase.ice_checking": "Establishing direct tunnel…",
|
||||
"transfer.phase.ice_connected": "Tunnel established",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"nav.themeToggle": "切换主题",
|
||||
"home.greeting": "你好,{{name}}",
|
||||
"home.deviceList.title": "设备",
|
||||
"home.device.wakeable": "可唤醒",
|
||||
"home.deviceList.onlineSuffix": "({{count}} 台在线)",
|
||||
"home.deviceList.showOffline": "显示离线设备({{count}})",
|
||||
"home.deviceList.hideOffline": "隐藏离线设备",
|
||||
@@ -256,6 +257,8 @@
|
||||
"transfer.mode.relay": "中继",
|
||||
"transfer.phase.initializing": "正在初始化…",
|
||||
"transfer.phase.waiting_accept": "等待对端接受…",
|
||||
"transfer.phase.waiting_peer": "等待对端上线…",
|
||||
"transfer.phase.peer_offline": "对端未上线",
|
||||
"transfer.phase.ice_gathering": "收集网络候选…",
|
||||
"transfer.phase.ice_checking": "建立直连通道…",
|
||||
"transfer.phase.ice_connected": "通道已连接",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"nav.themeToggle": "切換主題",
|
||||
"home.greeting": "你好,{{name}}",
|
||||
"home.deviceList.title": "裝置",
|
||||
"home.device.wakeable": "可喚醒",
|
||||
"home.deviceList.onlineSuffix": "({{count}} 部線上)",
|
||||
"home.deviceList.showOffline": "顯示離線裝置({{count}})",
|
||||
"home.deviceList.hideOffline": "隱藏離線裝置",
|
||||
@@ -256,6 +257,8 @@
|
||||
"transfer.mode.relay": "中繼",
|
||||
"transfer.phase.initializing": "正在初始化…",
|
||||
"transfer.phase.waiting_accept": "等待對端接受…",
|
||||
"transfer.phase.waiting_peer": "等待對端上線…",
|
||||
"transfer.phase.peer_offline": "對端未上線",
|
||||
"transfer.phase.ice_gathering": "收集網路候選…",
|
||||
"transfer.phase.ice_checking": "建立直連通道…",
|
||||
"transfer.phase.ice_connected": "通道已連線",
|
||||
|
||||
@@ -89,7 +89,7 @@ struct RootView: View
|
||||
{
|
||||
ForEach(engine.sendableDevices())
|
||||
{ dev in
|
||||
Button(dev.name) { engine.sendShares(to: dev.name) }
|
||||
Button(engine.sendLabel(dev)) { engine.sendShares(to: dev.name) }
|
||||
}
|
||||
Button(t("common.cancel"), role: .cancel) { engine.dismissShares() }
|
||||
}
|
||||
@@ -183,7 +183,7 @@ struct TransferListView: View
|
||||
{
|
||||
ForEach(sendableDevices)
|
||||
{ dev in
|
||||
Button(dev.name) { startSend(to: dev.name) }
|
||||
Button(engine.sendLabel(dev)) { startSend(to: dev.name) }
|
||||
}
|
||||
Button(t("common.cancel"), role: .cancel) { }
|
||||
}
|
||||
@@ -777,7 +777,7 @@ struct ReceivedFilesView: View
|
||||
{
|
||||
ForEach(engine.sendableDevices())
|
||||
{ dev in
|
||||
Button(dev.name) { send(to: dev.name) }
|
||||
Button(engine.sendLabel(dev)) { send(to: dev.name) }
|
||||
}
|
||||
Button(t("common.cancel"), role: .cancel) { }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user