diff --git a/ylgamehall/Resources/ChannelConfig.plist b/ylgamehall/Resources/ChannelConfig.plist
index 416ad9b..c7bd05c 100644
--- a/ylgamehall/Resources/ChannelConfig.plist
+++ b/ylgamehall/Resources/ChannelConfig.plist
@@ -17,7 +17,7 @@
agent
veRa0qrBf0df2K1G4de2tgfmVxB2jxpv
appversion
- 43
+ 44
other
appleconfig
diff --git a/ylgamehall/Source/WebView/WebContainerViewController.swift b/ylgamehall/Source/WebView/WebContainerViewController.swift
index d98d8b1..91b4c30 100644
--- a/ylgamehall/Source/WebView/WebContainerViewController.swift
+++ b/ylgamehall/Source/WebView/WebContainerViewController.swift
@@ -194,6 +194,21 @@ public final class WebContainerViewController: UIViewController {
// 异步 snapshot NWPath 区分"真断网"和"权限被拒",文案和按钮语义按 kind 精确分流。
let path = await Self.currentPathSnapshot()
let kind = Self.classifyBootError(error, path: path)
+
+ // 首次启动权限授予竞争:iOS 弹权限对话框时,pipeline 的第一个网络请求
+ // 已经在跑;如果用户没在请求 timeout 前点"允许",请求会 fail 且 NWPath
+ // 报 wifiDenied / cellularDenied —— 但用户可能"正准备点允许"。此时立
+ // 即弹"前往设置"会让用户困惑("我明明点了允许还弹")。
+ //
+ // 策略:只在 kind.isPermissionDenied 时,短暂观察 NWPath;5s 内若变
+ // satisfied → 静默重跑 pipeline(等价用户点允许后我们自动重试);超时
+ // 或仍拒绝 → 走原逻辑弹"前往设置"。真拒绝的长尾场景多等 5s 可接受。
+ if kind.isPermissionDenied,
+ await Self.waitForNetworkPermissionResolution(timeout: 5.0) {
+ await runBootPipeline()
+ return
+ }
+
presentBootError(error, kind: kind)
}
}
@@ -698,6 +713,34 @@ public final class WebContainerViewController: UIViewController {
monitor.start(queue: DispatchQueue.global(qos: .userInitiated))
}
}
+
+ /// 等首次启动权限对话框有决定 —— 最长 `timeout` 秒内观察 NWPath 是否变
+ /// `.satisfied`。用于消除"用户点允许太慢就弹前往设置"的误报体验。
+ /// 返回 true 表示已授权(可静默重试 pipeline);false 表示超时或明确拒绝
+ /// (应走原逻辑弹前往设置)。
+ private static func waitForNetworkPermissionResolution(timeout: TimeInterval) async -> Bool {
+ await withCheckedContinuation { (cont: CheckedContinuation) in
+ let monitor = NWPathMonitor()
+ nonisolated(unsafe) var resumed = false
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + timeout) {
+ guard !resumed else { return }
+ resumed = true
+ monitor.cancel()
+ cont.resume(returning: false)
+ }
+
+ monitor.pathUpdateHandler = { path in
+ guard !resumed else { return }
+ if path.status == .satisfied {
+ resumed = true
+ monitor.cancel()
+ cont.resume(returning: true)
+ }
+ }
+ monitor.start(queue: DispatchQueue.global(qos: .userInitiated))
+ }
+ }
}
// MARK: - WKNavigationDelegate