2caf1ab921
- 根因: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 的三返回值。
41 lines
1.7 KiB
SQL
41 lines
1.7 KiB
SQL
-- name: CreateTransferSession :exec
|
|
INSERT INTO transfer_sessions (
|
|
id, user_id, sender_name, receiver_name, state,
|
|
file_name, file_size, file_sha256, created_at
|
|
)
|
|
VALUES (?, ?, ?, ?, 'PENDING', ?, ?, ?, ?);
|
|
|
|
-- name: GetTransferSession :one
|
|
SELECT id, user_id, sender_name, receiver_name, state, mode,
|
|
file_name, file_size, file_sha256, bytes_transferred,
|
|
created_at, finished_at, fail_reason
|
|
FROM transfer_sessions
|
|
WHERE id = ?;
|
|
|
|
-- name: UpdateTransferState :execrows
|
|
UPDATE transfer_sessions
|
|
SET state = sqlc.arg('state'),
|
|
mode = COALESCE(sqlc.narg('mode'), mode),
|
|
fail_reason = COALESCE(sqlc.narg('fail_reason'), fail_reason),
|
|
finished_at = COALESCE(sqlc.narg('finished_at'), finished_at),
|
|
bytes_transferred = COALESCE(sqlc.narg('bytes_transferred'), bytes_transferred)
|
|
WHERE id = sqlc.arg('id');
|
|
|
|
-- name: ListPendingTransfersByReceiver :many
|
|
-- Still-PENDING transfers addressed to a receiver, bounded to recently created ones (the
|
|
-- sender's wait window). Re-delivered as transfer:incoming when that receiver (re)connects -
|
|
-- e.g. an offline iOS device just woken by the incoming-transfer push -so it arms its P2P
|
|
-- answerer for the waiting sender's offer.
|
|
SELECT id, sender_name, file_name, file_size, file_sha256
|
|
FROM transfer_sessions
|
|
WHERE user_id = ? AND receiver_name = ? AND state = 'PENDING' AND created_at > ?
|
|
ORDER BY created_at ASC;
|
|
|
|
-- name: ExpirePendingSessions :many
|
|
UPDATE transfer_sessions
|
|
SET state = 'CANCELLED',
|
|
fail_reason = 'pending_timeout',
|
|
finished_at = sqlc.arg('finished_at')
|
|
WHERE state = 'PENDING' AND created_at < sqlc.arg('cutoff')
|
|
RETURNING id, user_id, sender_name, receiver_name;
|