From 893f6740049de56aa1d9228aa4f4e9c6d29f9882 Mon Sep 17 00:00:00 2001 From: xweiba Date: Sun, 9 Aug 2026 01:07:25 +0800 Subject: [PATCH] docs: explain third-party client integration --- App/FirstSetupView.swift | 39 +++++++++++++++++++ README.en.md | 51 +++++++++++++++++++++++++ README.md | 47 +++++++++++++++++++++++ Shared/ThirdPartyProxyManager.swift | 4 +- Tests/readme_contract_test.sh | 8 ++++ Tests/third_party_mode_contract_test.sh | 10 +++++ 6 files changed, 157 insertions(+), 2 deletions(-) diff --git a/App/FirstSetupView.swift b/App/FirstSetupView.swift index eb7fb80..b521983 100644 --- a/App/FirstSetupView.swift +++ b/App/FirstSetupView.swift @@ -430,6 +430,45 @@ struct FirstSetupView: View { Text("除 Shadowrocket 外,当前客户端配置尚未完成真机验证,页面只提供模块导入入口和通用配置提醒。") .font(.footnote) .foregroundStyle(.secondary) + + DisclosureGroup("第三方客户端适配说明") { + VStack(alignment: .leading, spacing: 12) { + Text("工作原理") + .font(.subheadline.bold()) + Text("App 不连接远程坐标服务器,而是向 Apple 域名发起一个约定请求。第三方客户端需要在本机拦截该请求、保存 WGS-84 坐标并返回 JSON;定位模块再读取同一份数据,修改 Apple WLOC 响应。") + .font(.footnote) + .foregroundStyle(.secondary) + + Text("配置接口") + .font(.subheadline.bold()) + Text(ThirdPartyProxyManager.configurationEndpoint.absoluteString) + .font(.caption.monospaced()) + .textSelection(.enabled) + Text(""" + 查询:GET ?action=query + 保存:GET ?lon=<经度>&lat=<纬度>&acc=<精度> + 清除:GET ?action=clear + """) + .font(.caption.monospaced()) + .textSelection(.enabled) + + Text("返回格式") + .font(.subheadline.bold()) + Text(""" + 成功:{"success":true,"longitude":113.0,"latitude":22.0,"accuracy":25} + 失败:{"success":false,"error":"错误说明"} + """) + .font(.caption.monospaced()) + .textSelection(.enabled) + + Text("适配要求") + .font(.subheadline.bold()) + Text("客户端需要支持请求脚本、持久化存储、HTTP 200 JSON 响应、Apple WLOC 响应脚本,以及 gs-loc.apple.com / gs-loc-cn.apple.com 的 HTTPS 解密。保存接口和 WLOC 响应脚本必须读取同一份持久化数据。") + .font(.footnote) + .foregroundStyle(.secondary) + } + .padding(.top, 8) + } } } diff --git a/README.en.md b/README.en.md index b07861d..ed529e2 100644 --- a/README.en.md +++ b/README.en.md @@ -134,6 +134,57 @@ In this mode: - Wi-Fi, 4G, and 5G support depends on the client; - The configuration may remain active after Location Spoofer closes. +#### Configuration API and Client Integration + +The app does not upload coordinates to a project server. It sends the following GET request, which the third-party +client must intercept locally on the device: + +```text +https://gs-loc.apple.com/wloc-settings/save +``` + +| Action | Query parameters | Purpose | +|---|---|---| +| Query | `action=query` | Verify module connectivity and read the stored coordinate | +| Save | `lon=&lat=&acc=` | Store the selected coordinate | +| Clear | `action=clear` | Remove the stored test coordinate | + +The interception script must return HTTP 200 with JSON: + +```json +{ + "success": true, + "longitude": 113.0, + "latitude": 22.0, + "accuracy": 25 +} +``` + +Failures use: + +```json +{ + "success": false, + "error": "Error description" +} +``` + +When no coordinate is stored, a query may return `{"success":false,"error":"无已保存的坐标"}`. The app interprets +that response as “module connected, virtual location inactive.” A successful save response must echo the requested +WGS-84 longitude and latitude. + +To integrate another third-party client: + +1. Add an HTTP request script for `gs-loc.apple.com/wloc-settings/save` that parses the parameters and returns the + specified JSON; +2. Store coordinates, accuracy, and optional state in the client's persistent storage; +3. Enable HTTPS decryption for `gs-loc.apple.com` and `gs-loc-cn.apple.com`; +4. Intercept `gs-loc(-cn).apple.com/clls/wloc` responses, read the same persistent data, and modify the WLOC response; +5. Publish an importable module and verify query, save, clear, and location restoration on a real device. + +The app validates only the configuration endpoint's HTTP status, JSON shape, and coordinate round trip. It does not +manage the third-party client's certificate, MITM, VPN, or proxy state. + Do not enable App Mode interception and Third-party Proxy Mode interception at the same time. ## Runtime Modes diff --git a/README.md b/README.md index 549ba13..7442407 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,53 @@ WLOC 配置接口 - 是否支持 Wi-Fi、4G 或 5G 取决于客户端; - App 关闭后,第三方客户端中的配置可能继续生效。 +#### 配置接口与客户端适配 + +App 不会把坐标上传到项目服务器。它会发起以下 GET 请求,第三方客户端必须在设备本地拦截: + +```text +https://gs-loc.apple.com/wloc-settings/save +``` + +| 操作 | 查询参数 | 用途 | +|---|---|---| +| 查询 | `action=query` | 检查模块是否连接,并读取当前保存的坐标 | +| 保存 | `lon=&lat=&acc=<精度>` | 保存当前选点 | +| 清除 | `action=clear` | 删除已保存的测试坐标 | + +拦截脚本必须返回 HTTP 200 和 JSON: + +```json +{ + "success": true, + "longitude": 113.0, + "latitude": 22.0, + "accuracy": 25 +} +``` + +失败时返回: + +```json +{ + "success": false, + "error": "错误说明" +} +``` + +查询时没有已保存坐标,可以返回 `{"success":false,"error":"无已保存的坐标"}`。App 会把它识别为 +“模块已连接,但虚拟定位未开启”。保存成功时,响应中的经纬度必须与请求中的 WGS-84 坐标一致。 + +要适配新的第三方客户端,需要: + +1. 为 `gs-loc.apple.com/wloc-settings/save` 添加 HTTP 请求脚本,解析上述参数并返回约定 JSON; +2. 使用客户端的持久化存储保存坐标、精度和可选状态; +3. 为 `gs-loc.apple.com` 和 `gs-loc-cn.apple.com` 配置 HTTPS 解密; +4. 拦截 `gs-loc(-cn).apple.com/clls/wloc` 响应,读取同一份持久化数据并修改 WLOC 响应; +5. 提供可订阅的模块文件,并确认查询、保存、清除和定位恢复都能在真机完成。 + +App 只验证配置接口的 HTTP 状态、JSON 格式和坐标回读,不管理第三方客户端的证书、MITM、VPN 或代理状态。 + 不要同时启用 APP 模式代理和第三方代理模式,避免两个代理链路互相干扰。 ## 运行模式 diff --git a/Shared/ThirdPartyProxyManager.swift b/Shared/ThirdPartyProxyManager.swift index 67b7444..8a4d95b 100644 --- a/Shared/ThirdPartyProxyManager.swift +++ b/Shared/ThirdPartyProxyManager.swift @@ -47,12 +47,12 @@ extension URLSession: ThirdPartyProxyRequesting {} final class ThirdPartyProxyManager: ObservableObject { static let shared = ThirdPartyProxyManager() static let interceptionHostname = "gs-loc.apple.com" + static let configurationEndpoint = URL(string: "https://gs-loc.apple.com/wloc-settings/save")! @Published private(set) var connectionState: ThirdPartyProxyConnectionState = .unknown @Published private(set) var activeSettings: ThirdPartyProxySettingsResponse? @Published private(set) var isRequesting = false private let requester: any ThirdPartyProxyRequesting - private let endpoint = URL(string: "https://gs-loc.apple.com/wloc-settings/save")! init(requester: (any ThirdPartyProxyRequesting)? = nil) { if let requester { @@ -134,7 +134,7 @@ final class ThirdPartyProxyManager: ObservableObject { isRequesting = true defer { isRequesting = false } - var components = URLComponents(url: endpoint, resolvingAgainstBaseURL: false)! + var components = URLComponents(url: Self.configurationEndpoint, resolvingAgainstBaseURL: false)! switch action { case .query: components.queryItems = [URLQueryItem(name: "action", value: "query")] diff --git a/Tests/readme_contract_test.sh b/Tests/readme_contract_test.sh index 8dfcafe..0987e2a 100755 --- a/Tests/readme_contract_test.sh +++ b/Tests/readme_contract_test.sh @@ -53,6 +53,14 @@ test "$(grep -c '^## ' "$ZH")" -eq "$(grep -c '^## ' "$EN")" \ grep -q '当前项目不支持在 Windows 上直接构建 iOS 应用' "$ZH" || fail "Chinese README must reject Windows source builds" grep -q 'Building the iOS app directly on Windows is not supported' "$EN" || fail "English README must reject Windows source builds" grep -q 'docs/COMMUNITY_TUTORIALS.md' "$ZH" || fail "Chinese README must link the community tutorial submission guide" +grep -q '^#### 配置接口与客户端适配$' "$ZH" \ + || fail "Chinese README must document the third-party integration contract" +grep -q '^#### Configuration API and Client Integration$' "$EN" \ + || fail "English README must document the third-party integration contract" +for contract in 'action=query' 'action=clear' 'lon=&lat=<纬度>&acc=<精度>' "$SETUP" \ + || fail "client integration guidance must document WGS-84 coordinate saving" +grep -Fq '清除:GET ?action=clear' "$SETUP" \ + || fail "client integration guidance must document the clear action" for file in wloc.module wloc.sgmodule wloc.conf wloc.lpx wloc.stoverride; do test -s "$MODULES/$file" || fail "missing bundled module: $file"