Pin external actions, images, toolchains, native archives, and tvOS engine artifacts; enforce fail-closed CI checks and keep website privacy disclosures aligned with shipped behavior.
106 lines
3.4 KiB
Swift
106 lines
3.4 KiB
Swift
import Foundation
|
|
|
|
/// Keeps tvOS launcher events single-shot without suppressing a deliberate
|
|
/// second tap forever. All access is confined to the main thread by the host.
|
|
final class TvosDeepLinkDeliveryCoordinator {
|
|
struct Event: Equatable {
|
|
let contentId: String
|
|
let receivedAt: TimeInterval
|
|
var flutterArguments: [String: String] { ["contentId": contentId] }
|
|
fileprivate let sequence: UInt64
|
|
}
|
|
|
|
enum Receipt: Equatable {
|
|
case retained
|
|
case duplicate
|
|
case deliver(Event)
|
|
}
|
|
|
|
private let deduplicationWindow: TimeInterval
|
|
private let now: () -> TimeInterval
|
|
private var sequence: UInt64 = 0
|
|
private var liveDeliveryEnabled = false
|
|
private var pending: Event?
|
|
private var inFlight: Event?
|
|
private var lastDelivered: Event?
|
|
|
|
init(
|
|
deduplicationWindow: TimeInterval = 2,
|
|
now: @escaping () -> TimeInterval = { ProcessInfo.processInfo.systemUptime }
|
|
) {
|
|
precondition(deduplicationWindow >= 0)
|
|
self.deduplicationWindow = deduplicationWindow
|
|
self.now = now
|
|
}
|
|
|
|
/// A replacement Flutter engine must perform its initial read before live
|
|
/// delivery resumes. A launch event captured before registration is retained.
|
|
func bindEngine() {
|
|
liveDeliveryEnabled = false
|
|
inFlight = nil
|
|
}
|
|
|
|
func receive(contentId: String) -> Receipt {
|
|
let timestamp = now()
|
|
|
|
if let inFlight, isDuplicate(contentId: contentId, at: timestamp, of: inFlight) {
|
|
return .duplicate
|
|
}
|
|
if let pending, isDuplicate(contentId: contentId, at: timestamp, of: pending) {
|
|
// A retained failed delivery is retryable when no send is active.
|
|
return liveDeliveryEnabled && inFlight == nil ? beginDelivery(pending) : .duplicate
|
|
}
|
|
if let lastDelivered, isDuplicate(contentId: contentId, at: timestamp, of: lastDelivered) {
|
|
return .duplicate
|
|
}
|
|
|
|
sequence &+= 1
|
|
let event = Event(contentId: contentId, receivedAt: timestamp, sequence: sequence)
|
|
pending = event
|
|
guard liveDeliveryEnabled, inFlight == nil else { return .retained }
|
|
return beginDelivery(event)
|
|
}
|
|
|
|
/// Atomically transitions an engine from retained launch delivery to live
|
|
/// delivery and returns the one retained event, if any.
|
|
func beginLiveDelivery() -> Event? {
|
|
liveDeliveryEnabled = true
|
|
guard inFlight == nil, let pending else { return nil }
|
|
inFlight = pending
|
|
return pending
|
|
}
|
|
|
|
/// Clears a retained event only after Flutter reports successful delivery.
|
|
/// A newer retained event is returned so callers can deliver it next.
|
|
func complete(_ event: Event, succeeded: Bool) -> Event? {
|
|
guard inFlight == event else { return nil }
|
|
inFlight = nil
|
|
guard succeeded else { return nil }
|
|
|
|
lastDelivered = event
|
|
if pending == event {
|
|
pending = nil
|
|
}
|
|
guard liveDeliveryEnabled, let pending else { return nil }
|
|
return beginDelivery(pending).event
|
|
}
|
|
|
|
var retainedContentId: String? { pending?.contentId }
|
|
|
|
private func beginDelivery(_ event: Event) -> Receipt {
|
|
inFlight = event
|
|
return .deliver(event)
|
|
}
|
|
|
|
private func isDuplicate(contentId: String, at timestamp: TimeInterval, of event: Event) -> Bool {
|
|
contentId == event.contentId && timestamp - event.receivedAt <= deduplicationWindow
|
|
}
|
|
}
|
|
|
|
private extension TvosDeepLinkDeliveryCoordinator.Receipt {
|
|
var event: TvosDeepLinkDeliveryCoordinator.Event? {
|
|
if case let .deliver(event) = self { return event }
|
|
return nil
|
|
}
|
|
}
|