mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-25 16:11:54 +08:00
chore: prepare v1.0.5 release
This commit is contained in:
@@ -23,7 +23,7 @@ struct AppRemoteConfiguration: Decodable, Equatable {
|
||||
let communityPromptClients: [String]
|
||||
|
||||
static let fallback = AppRemoteConfiguration(
|
||||
latestVersion: "1.0.4",
|
||||
latestVersion: "1.0.5",
|
||||
minimumSupportedVersion: "1.0.0",
|
||||
communityPromptClients: [
|
||||
ThirdPartyProxyClient.surge.rawValue,
|
||||
@@ -107,87 +107,99 @@ final class AppRemoteConfigurationStore: ObservableObject {
|
||||
}
|
||||
|
||||
enum AppRemoteConfigurationService {
|
||||
static let configurationURL = URL(
|
||||
string: "https://raw.githubusercontent.com/xweiba/location-spoofer/main/version.txt"
|
||||
)!
|
||||
static let configurationURLs = [
|
||||
URL(
|
||||
string: "https://gh-proxy.org/https://raw.githubusercontent.com/xweiba/location-spoofer/main/version.txt"
|
||||
)!,
|
||||
URL(
|
||||
string: "https://raw.githubusercontent.com/xweiba/location-spoofer/main/version.txt"
|
||||
)!
|
||||
]
|
||||
static let releasesURL = URL(
|
||||
string: "https://github.com/xweiba/location-spoofer/releases/latest"
|
||||
)!
|
||||
|
||||
static func fetch() async -> AppRemoteConfiguration? {
|
||||
let sessionConfiguration = URLSessionConfiguration.ephemeral
|
||||
sessionConfiguration.timeoutIntervalForRequest = 1.5
|
||||
sessionConfiguration.timeoutIntervalForResource = 2
|
||||
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
sessionConfiguration.waitsForConnectivity = false
|
||||
let session = URLSession(configuration: sessionConfiguration)
|
||||
let session = makeSession()
|
||||
defer { session.finishTasksAndInvalidate() }
|
||||
|
||||
var request = URLRequest(url: configurationURL)
|
||||
request.timeoutInterval = 1.5
|
||||
for url in configurationURLs {
|
||||
var request = URLRequest(url: url)
|
||||
request.timeoutInterval = 1.5
|
||||
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
RuntimeLogger.warning("APP", "Update", "版本配置请求返回非 200,继续使用内置配置")
|
||||
return nil
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
continue
|
||||
}
|
||||
let configuration = try AppRemoteConfiguration.decode(data)
|
||||
RuntimeLogger.info("APP", "Update", "远程版本配置加载成功", details: [
|
||||
"来源": url.host ?? "未知",
|
||||
"最新版本": configuration.latestVersion,
|
||||
"最低版本": configuration.minimumSupportedVersion,
|
||||
"社区征集客户端数": String(configuration.communityPromptClients.count)
|
||||
])
|
||||
return configuration
|
||||
} catch {
|
||||
RuntimeLogger.info("APP", "Update", "版本配置源不可用,尝试下一地址", details: [
|
||||
"来源": url.host ?? "未知",
|
||||
"错误": error.localizedDescription
|
||||
])
|
||||
}
|
||||
let configuration = try AppRemoteConfiguration.decode(data)
|
||||
RuntimeLogger.info("APP", "Update", "远程版本配置加载成功", details: [
|
||||
"最新版本": configuration.latestVersion,
|
||||
"最低版本": configuration.minimumSupportedVersion,
|
||||
"社区征集客户端数": String(configuration.communityPromptClients.count)
|
||||
])
|
||||
return configuration
|
||||
} catch {
|
||||
RuntimeLogger.warning("APP", "Update", "版本配置加载失败,继续使用内置配置", details: [
|
||||
"错误": error.localizedDescription
|
||||
])
|
||||
return nil
|
||||
}
|
||||
|
||||
RuntimeLogger.warning("APP", "Update", "版本配置加载失败,继续使用内置配置")
|
||||
return nil
|
||||
}
|
||||
|
||||
static func fetchReleaseNotes(version: String) async -> String? {
|
||||
guard let url = URL(
|
||||
string: "https://raw.githubusercontent.com/xweiba/location-spoofer/main/docs/releases/v\(version).md"
|
||||
) else {
|
||||
return nil
|
||||
let session = makeSession()
|
||||
defer { session.finishTasksAndInvalidate() }
|
||||
|
||||
for url in releaseNotesURLs(version: version) {
|
||||
var request = URLRequest(url: url)
|
||||
request.timeoutInterval = 1.5
|
||||
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200,
|
||||
let markdown = String(data: data, encoding: .utf8),
|
||||
let summary = releaseNotesSummary(markdown) else {
|
||||
continue
|
||||
}
|
||||
return summary
|
||||
} catch {
|
||||
RuntimeLogger.info("APP", "Update", "版本说明源不可用,尝试下一地址", details: [
|
||||
"来源": url.host ?? "未知",
|
||||
"版本": version,
|
||||
"错误": error.localizedDescription
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeLogger.info("APP", "Update", "版本说明加载失败,将使用最新 Release 页面", details: [
|
||||
"版本": version
|
||||
])
|
||||
return nil
|
||||
}
|
||||
|
||||
static func releaseNotesURLs(version: String) -> [URL] {
|
||||
let path = "https://raw.githubusercontent.com/xweiba/location-spoofer/main/docs/releases/v\(version).md"
|
||||
return [
|
||||
URL(string: "https://gh-proxy.org/\(path)")!,
|
||||
URL(string: path)!
|
||||
]
|
||||
}
|
||||
|
||||
private static func makeSession() -> URLSession {
|
||||
let sessionConfiguration = URLSessionConfiguration.ephemeral
|
||||
sessionConfiguration.timeoutIntervalForRequest = 1.5
|
||||
sessionConfiguration.timeoutIntervalForResource = 2
|
||||
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
|
||||
sessionConfiguration.waitsForConnectivity = false
|
||||
let session = URLSession(configuration: sessionConfiguration)
|
||||
defer { session.finishTasksAndInvalidate() }
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.timeoutInterval = 1.5
|
||||
|
||||
do {
|
||||
let (data, response) = try await session.data(for: request)
|
||||
guard let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 else {
|
||||
RuntimeLogger.info("APP", "Update", "版本说明不存在,将使用最新 Release 页面", details: [
|
||||
"版本": version
|
||||
])
|
||||
return nil
|
||||
}
|
||||
guard let markdown = String(data: data, encoding: .utf8) else {
|
||||
RuntimeLogger.info("APP", "Update", "版本说明编码无效,将使用最新 Release 页面", details: [
|
||||
"版本": version
|
||||
])
|
||||
return nil
|
||||
}
|
||||
return releaseNotesSummary(markdown)
|
||||
} catch {
|
||||
RuntimeLogger.info("APP", "Update", "版本说明加载失败,将使用最新 Release 页面", details: [
|
||||
"版本": version,
|
||||
"错误": error.localizedDescription
|
||||
])
|
||||
return nil
|
||||
}
|
||||
return URLSession(configuration: sessionConfiguration)
|
||||
}
|
||||
|
||||
private static func releaseNotesSummary(_ markdown: String) -> String? {
|
||||
|
||||
@@ -54,6 +54,20 @@ enum ThirdPartyProxyError: LocalizedError, Equatable {
|
||||
return "模块版本过低,请重新导入模块"
|
||||
}
|
||||
}
|
||||
|
||||
var recoverySuggestion: String {
|
||||
switch self {
|
||||
case .moduleOutdated:
|
||||
return "删除旧模块后,重新复制并导入最新模块"
|
||||
default:
|
||||
return "检查模块、MITM、证书和代理/VPN连接"
|
||||
}
|
||||
}
|
||||
|
||||
static func recoverySuggestion(for error: Error) -> String {
|
||||
(error as? Self)?.recoverySuggestion
|
||||
?? "检查模块、MITM、证书和代理/VPN连接"
|
||||
}
|
||||
}
|
||||
|
||||
protocol ThirdPartyProxyRequesting {
|
||||
@@ -65,12 +79,17 @@ extension URLSession: ThirdPartyProxyRequesting {}
|
||||
@MainActor
|
||||
final class ThirdPartyProxyManager: ObservableObject {
|
||||
static let shared = ThirdPartyProxyManager()
|
||||
static let interceptionHostname = "gs-loc.apple.com"
|
||||
static let interceptionHostnames = [
|
||||
"gs-loc.apple.com",
|
||||
"gs-loc-cn.apple.com"
|
||||
]
|
||||
static let interceptionHostnamesText = interceptionHostnames.joined(separator: ", ")
|
||||
static let configurationEndpoint = URL(string: "https://gs-loc.apple.com/wloc-settings/save")!
|
||||
static let versionEndpoint = URL(string: "https://gs-loc.apple.com/wloc-settings/version")!
|
||||
|
||||
@Published private(set) var connectionState: ThirdPartyProxyConnectionState = .unknown
|
||||
@Published private(set) var activeSettings: ThirdPartyProxySettingsResponse?
|
||||
@Published private(set) var moduleUpdateRecommended = false
|
||||
@Published private(set) var isRequesting = false
|
||||
private let requester: any ThirdPartyProxyRequesting
|
||||
|
||||
@@ -89,24 +108,18 @@ final class ThirdPartyProxyManager: ObservableObject {
|
||||
|
||||
func query() async throws -> ThirdPartyProxySettingsResponse {
|
||||
let response = try await perform(action: .query)
|
||||
if response.success,
|
||||
response.latitude != nil,
|
||||
response.longitude != nil {
|
||||
let active = try validatedQueryState(response)
|
||||
if active {
|
||||
activeSettings = response
|
||||
connectionState = .connected(active: true)
|
||||
} else if response.error?.contains("无已保存") == true {
|
||||
} else {
|
||||
activeSettings = nil
|
||||
connectionState = .connected(active: false)
|
||||
} else {
|
||||
let error = ThirdPartyProxyError.rejected(response.error ?? "第三方代理查询失败")
|
||||
connectionState = .failed(error.localizedDescription)
|
||||
throw error
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func save(_ favorite: FavoriteLocation) async throws -> ThirdPartyProxySettingsResponse {
|
||||
_ = try await validateVersion()
|
||||
let wgs84 = favorite.coordinatePair.wgs84
|
||||
let response = try await perform(action: .save(
|
||||
latitude: wgs84.latitude,
|
||||
@@ -139,7 +152,9 @@ final class ThirdPartyProxyManager: ObservableObject {
|
||||
let longitude = current.longitude else {
|
||||
throw ThirdPartyProxyError.rejected("第三方虚拟定位尚未开启")
|
||||
}
|
||||
_ = try await validateVersion()
|
||||
guard await refreshAdvancedFeatureAvailability() else {
|
||||
throw ThirdPartyProxyError.moduleOutdated
|
||||
}
|
||||
let response = try await perform(action: .save(
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
@@ -153,6 +168,10 @@ final class ThirdPartyProxyManager: ObservableObject {
|
||||
return response
|
||||
}
|
||||
|
||||
func validateConnection() async throws -> ThirdPartyProxySettingsResponse {
|
||||
try await query()
|
||||
}
|
||||
|
||||
func validateVersion() async throws -> ThirdPartyProxyVersionResponse {
|
||||
guard !isRequesting else {
|
||||
throw ThirdPartyProxyError.rejected("已有第三方代理请求正在执行")
|
||||
@@ -184,6 +203,27 @@ final class ThirdPartyProxyManager: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func refreshAdvancedFeatureAvailability() async -> Bool {
|
||||
do {
|
||||
_ = try await validateVersion()
|
||||
moduleUpdateRecommended = false
|
||||
return true
|
||||
} catch {
|
||||
moduleUpdateRecommended = true
|
||||
RuntimeLogger.warning(
|
||||
"APP",
|
||||
"ThirdPartyProxy",
|
||||
"第三方模块不支持高级功能",
|
||||
details: [
|
||||
"版本检测": error.localizedDescription,
|
||||
"处理建议": "基础坐标功能可继续使用;更新模块后可使用运动状态模拟"
|
||||
]
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func clear() async throws {
|
||||
let response = try await perform(action: .clear)
|
||||
guard response.success else {
|
||||
@@ -194,6 +234,18 @@ final class ThirdPartyProxyManager: ObservableObject {
|
||||
RuntimeLogger.info("APP", "ThirdPartyProxy", "第三方代理坐标已清除")
|
||||
}
|
||||
|
||||
private func validatedQueryState(_ response: ThirdPartyProxySettingsResponse) throws -> Bool {
|
||||
if response.success,
|
||||
response.latitude != nil,
|
||||
response.longitude != nil {
|
||||
return true
|
||||
}
|
||||
if response.error?.contains("无已保存") == true {
|
||||
return false
|
||||
}
|
||||
throw ThirdPartyProxyError.rejected(response.error ?? "第三方代理查询失败")
|
||||
}
|
||||
|
||||
private enum Action {
|
||||
case query
|
||||
case save(latitude: Double, longitude: Double, accuracy: Int, motionEnabled: Bool)
|
||||
|
||||
Reference in New Issue
Block a user