feat: improve runtime setup and guidance

This commit is contained in:
xweiba
2026-08-08 15:18:40 +08:00
parent 3499f080d7
commit b07fa61195
54 changed files with 1794 additions and 325 deletions
+133 -41
View File
@@ -13,6 +13,7 @@ struct SettingsView: View {
@State private var proxyOperationAlertTitle = "代理操作失败"
@State private var modeOperationRunning = false
@State private var copiedClient: ThirdPartyProxyClient?
@State private var showCertificateResetConfirmation = false
var body: some View {
Form {
@@ -25,11 +26,6 @@ struct SettingsView: View {
.pickerStyle(.inline)
.disabled(modeOperationRunning || actions.state.isBusy || thirdPartyProxy.isRequesting)
if runtimeMode.mode == .thirdParty {
Label("测试模式:仅 Shadowrocket 当前可测试", systemImage: "testtube.2")
.font(.footnote)
.foregroundStyle(.orange)
}
}
Section("状态") {
@@ -69,22 +65,23 @@ struct SettingsView: View {
thirdPartyConfigurationSection
} else {
Section("说明") {
Button {
activeTip = .activation
} label: {
Label("生效说明", systemImage: "checklist")
}
Button {
activeTip = .deactivation
} label: {
Label("失效说明", systemImage: "arrow.uturn.backward.circle")
}
Button {
activeTip = .removeProxy
} label: {
Label("关闭 WiFi 代理", systemImage: "wifi.slash")
}
Button {
activeTip = .activation
} label: {
Label("生效说明", systemImage: "checklist")
}
Button {
activeTip = .deactivation
} label: {
Label("失效说明", systemImage: "arrow.uturn.backward.circle")
}
Button {
activeTip = .removeProxy
} label: {
Label("关闭 WiFi 代理", systemImage: "wifi.slash")
}
}
}
Section("工作原理") {
@@ -104,6 +101,21 @@ struct SettingsView: View {
valueRow("版本", value: versionText)
}
if runtimeMode.mode == .localWiFi {
Section("证书") {
Button(role: .destructive) {
showCertificateResetConfirmation = true
} label: {
Label("重置证书", systemImage: "arrow.clockwise.circle")
}
.disabled(modeOperationRunning || actions.state.isBusy)
Text("仅删除 App 钥匙串中的设备 CA。iOS 中已经安装的旧证书需要在系统设置里手动移除。")
.font(.footnote)
.foregroundStyle(.secondary)
}
}
Section("支持") {
NavigationLink {
BugReportView(setup: setup)
@@ -152,6 +164,18 @@ struct SettingsView: View {
} message: {
Text(proxyOperationError)
}
.confirmationDialog(
"重置证书?",
isPresented: $showCertificateResetConfirmation,
titleVisibility: .visible
) {
Button("重置并生成新证书", role: .destructive) {
resetCertificateAuthority()
}
Button("取消", role: .cancel) {}
} message: {
Text("当前虚拟定位和本地代理将停止。App 会删除钥匙串中的设备 CA、立即生成新证书,并打开安装与信任引导。你还需要前往 iOS「设置 → 通用 → VPN 与设备管理」手动删除旧证书,然后重新下载安装并完全信任新证书。")
}
}
private func valueRow(_ title: String, value: String) -> some View {
@@ -205,19 +229,21 @@ struct SettingsView: View {
}
}
HStack {
Text("验证状态")
Spacer()
Text(thirdPartyClient.selectedClient.verificationText)
.font(.footnote)
.foregroundStyle(thirdPartyClient.selectedClient == .shadowrocket ? .green : .orange)
if let verificationText = thirdPartyClient.selectedClient.verificationText {
HStack {
Text("验证状态")
Spacer()
Text(verificationText)
.font(.footnote)
.foregroundStyle(.orange)
}
}
Button {
UIPasteboard.general.string = thirdPartyClient.selectedClient.subscriptionURL.absoluteString
copiedClient = thirdPartyClient.selectedClient
} label: {
Label(copiedClient == thirdPartyClient.selectedClient ? "已复制订阅链接" : "复制订阅链接", systemImage: "doc.on.doc")
Label(copiedClient == thirdPartyClient.selectedClient ? "已复制模块订阅地址" : "复制模块订阅地址", systemImage: "doc.on.doc")
}
Button {
@@ -227,7 +253,7 @@ struct SettingsView: View {
}
Button {
setup.requestThirdPartySetup()
setup.requestThirdPartyOnboarding()
dismiss()
} label: {
Label("重新打开配置引导", systemImage: "arrow.clockwise.circle")
@@ -241,7 +267,7 @@ struct SettingsView: View {
.font(.footnote).foregroundStyle(.secondary)
}
Text("复制链接后,在对应代理客户端中添加模块/重写订阅,并启用 MITM。第三方客户端保存坐标后,即使关闭本 App,坐标仍由代理客户端持久化并继续生效。")
Text("复制模块订阅地址后,在对应代理客户端中添加模块/重写订阅,并启用 MITM。第三方客户端保存坐标后,即使关闭本 App,坐标仍由代理客户端持久化并继续生效。")
.font(.footnote).foregroundStyle(.secondary)
}
}
@@ -294,9 +320,18 @@ struct SettingsView: View {
proxy.stop()
setup.completeSetup()
runtimeMode.setMode(.thirdParty)
setup.requestThirdPartySetup()
proxyOperationAlertTitle = "模式已切换"
proxyOperationError = "已切换到第三方代理模式。请关闭 Wi-Fi 中的 127.0.0.1:8888 手动代理,并按引导导入第三方配置。"
if runtimeMode.isInitialized(.thirdParty) {
do {
_ = try await thirdPartyProxy.query()
proxyOperationAlertTitle = "模式已切换"
proxyOperationError = "第三方代理模式检测通过。请关闭 Wi-Fi 中的 127.0.0.1:8888 手动代理,避免双重拦截。"
} catch {
openThirdPartySetup(for: error)
}
} else {
setup.requestThirdPartyOnboarding()
dismiss()
}
case .localWiFi:
do {
try await thirdPartyProxy.clear()
@@ -307,23 +342,80 @@ struct SettingsView: View {
}
runtimeMode.setMode(.localWiFi)
await setup.prepareLocalServices()
setup.requestSetup()
proxyOperationAlertTitle = "模式已切换"
proxyOperationError = "已切换到 APP 模式。请停用第三方 WLOC 模块或代理连接,避免双重拦截。"
if runtimeMode.isInitialized(.localWiFi) {
let result = await setup.runVerificationTest()
setup.applyVerificationResult(result)
if result.isSuccess {
proxyOperationAlertTitle = "模式已切换"
proxyOperationError = "APP 模式环境检测通过。请停用第三方 WLOC 模块或代理连接,避免双重拦截。"
} else {
dismiss()
}
} else {
setup.requestSetup()
dismiss()
}
}
}
}
private func detectThirdPartyConnection() {
let client = thirdPartyClient.selectedClient
let startedAt = Date()
Task { @MainActor in
do {
let response = try await thirdPartyProxy.query()
if !response.success, response.error?.contains("无已保存") != true {
proxyOperationAlertTitle = "检测失败"
proxyOperationError = response.error ?? "第三方代理模块返回失败"
}
_ = try await thirdPartyProxy.query()
RuntimeLogger.info("APP", "ThirdPartyProxy", "设置页第三方连接检测通过", details: [
"当前客户端": client.name,
"请求动作": "WLOC query",
"耗时毫秒": String(Int(Date().timeIntervalSince(startedAt) * 1_000))
])
runtimeMode.markInitialized(.thirdParty)
} catch {
proxyOperationAlertTitle = "检测失败"
RuntimeLogger.error(
"APP",
"ThirdPartyProxy",
"设置页第三方连接检测失败",
error: error,
details: [
"当前客户端": client.name,
"请求动作": "WLOC query",
"连接状态": String(describing: thirdPartyProxy.connectionState),
"耗时毫秒": String(Int(Date().timeIntervalSince(startedAt) * 1_000)),
"处理建议": "检查模块、MITM、证书和代理/VPN连接"
]
)
openThirdPartySetup(for: error)
}
}
}
private func openThirdPartySetup(for error: Error) {
setup.requestThirdPartySetup(message: error.localizedDescription)
dismiss()
}
private func resetCertificateAuthority() {
guard runtimeMode.mode == .localWiFi, !modeOperationRunning else { return }
modeOperationRunning = true
Task { @MainActor in
defer { modeOperationRunning = false }
if actions.virtualLocationEnabled {
actions.clear()
}
proxy.stop()
do {
try setup.certificateStore.reset()
runtimeMode.resetInitialization(.localWiFi)
guard await setup.prepareLocalServices() else {
proxyOperationAlertTitle = "证书重置失败"
proxyOperationError = setup.message
return
}
setup.requestCertificateSetup()
dismiss()
} catch {
proxyOperationAlertTitle = "证书重置失败"
proxyOperationError = error.localizedDescription
}
}