docs: explain third-party client integration

This commit is contained in:
xweiba
2026-08-09 01:07:25 +08:00
parent 8cc65172b2
commit 893f674004
6 changed files with 157 additions and 2 deletions
+39
View File
@@ -430,6 +430,45 @@ struct FirstSetupView: View {
Text("除 Shadowrocket 外,当前客户端配置尚未完成真机验证,页面只提供模块导入入口和通用配置提醒。") Text("除 Shadowrocket 外,当前客户端配置尚未完成真机验证,页面只提供模块导入入口和通用配置提醒。")
.font(.footnote) .font(.footnote)
.foregroundStyle(.secondary) .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)
}
} }
} }
+51
View File
@@ -134,6 +134,57 @@ In this mode:
- Wi-Fi, 4G, and 5G support depends on the client; - Wi-Fi, 4G, and 5G support depends on the client;
- The configuration may remain active after Location Spoofer closes. - 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=<WGS-84 longitude>&lat=<WGS-84 latitude>&acc=<accuracy>` | 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. Do not enable App Mode interception and Third-party Proxy Mode interception at the same time.
## Runtime Modes ## Runtime Modes
+47
View File
@@ -130,6 +130,53 @@ WLOC 配置接口
- 是否支持 Wi-Fi、4G 或 5G 取决于客户端; - 是否支持 Wi-Fi、4G 或 5G 取决于客户端;
- App 关闭后,第三方客户端中的配置可能继续生效。 - App 关闭后,第三方客户端中的配置可能继续生效。
#### 配置接口与客户端适配
App 不会把坐标上传到项目服务器。它会发起以下 GET 请求,第三方客户端必须在设备本地拦截:
```text
https://gs-loc.apple.com/wloc-settings/save
```
| 操作 | 查询参数 | 用途 |
|---|---|---|
| 查询 | `action=query` | 检查模块是否连接,并读取当前保存的坐标 |
| 保存 | `lon=<WGS-84 经度>&lat=<WGS-84 纬度>&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 模式代理和第三方代理模式,避免两个代理链路互相干扰。 不要同时启用 APP 模式代理和第三方代理模式,避免两个代理链路互相干扰。
## 运行模式 ## 运行模式
+2 -2
View File
@@ -47,12 +47,12 @@ extension URLSession: ThirdPartyProxyRequesting {}
final class ThirdPartyProxyManager: ObservableObject { final class ThirdPartyProxyManager: ObservableObject {
static let shared = ThirdPartyProxyManager() static let shared = ThirdPartyProxyManager()
static let interceptionHostname = "gs-loc.apple.com" 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 connectionState: ThirdPartyProxyConnectionState = .unknown
@Published private(set) var activeSettings: ThirdPartyProxySettingsResponse? @Published private(set) var activeSettings: ThirdPartyProxySettingsResponse?
@Published private(set) var isRequesting = false @Published private(set) var isRequesting = false
private let requester: any ThirdPartyProxyRequesting private let requester: any ThirdPartyProxyRequesting
private let endpoint = URL(string: "https://gs-loc.apple.com/wloc-settings/save")!
init(requester: (any ThirdPartyProxyRequesting)? = nil) { init(requester: (any ThirdPartyProxyRequesting)? = nil) {
if let requester { if let requester {
@@ -134,7 +134,7 @@ final class ThirdPartyProxyManager: ObservableObject {
isRequesting = true isRequesting = true
defer { isRequesting = false } defer { isRequesting = false }
var components = URLComponents(url: endpoint, resolvingAgainstBaseURL: false)! var components = URLComponents(url: Self.configurationEndpoint, resolvingAgainstBaseURL: false)!
switch action { switch action {
case .query: case .query:
components.queryItems = [URLQueryItem(name: "action", value: "query")] components.queryItems = [URLQueryItem(name: "action", value: "query")]
+8
View File
@@ -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 '当前项目不支持在 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 '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 '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=<WGS-84'; do
grep -q "$contract" "$ZH" || fail "Chinese README is missing third-party contract: $contract"
grep -q "$contract" "$EN" || fail "English README is missing third-party contract: $contract"
done
grep -q '除敏感信息遮挡外,不要自行添加箭头、编号、边框、说明文字或其他标注' \ grep -q '除敏感信息遮挡外,不要自行添加箭头、编号、边框、说明文字或其他标注' \
"$ROOT/docs/COMMUNITY_TUTORIALS.md" \ "$ROOT/docs/COMMUNITY_TUTORIALS.md" \
|| fail "tutorial submissions must keep source screenshots free of non-privacy annotations" || fail "tutorial submissions must keep source screenshots free of non-privacy annotations"
+10
View File
@@ -27,6 +27,16 @@ grep -Fq 'Label("打开 \(client.name)"' "$SETUP" \
grep -Fq 'Label("打开 \(thirdPartyClient.selectedClient.name)"' "$SETTINGS" || fail "Settings must expose a client launch action" grep -Fq 'Label("打开 \(thirdPartyClient.selectedClient.name)"' "$SETTINGS" || fail "Settings must expose a client launch action"
! grep -q '在浏览器打开模块文件' "$SETTINGS" || fail "Settings must not open the module URL as the primary client action" ! grep -q '在浏览器打开模块文件' "$SETTINGS" || fail "Settings must not open the module URL as the primary client action"
grep -q 'requestThirdPartySetup' "$SETTINGS" || fail "Settings must reopen third-party setup" grep -q 'requestThirdPartySetup' "$SETTINGS" || fail "Settings must reopen third-party setup"
grep -q 'static let configurationEndpoint' "$MANAGER" \
|| fail "the third-party configuration endpoint must have one shared owner"
grep -q 'DisclosureGroup("第三方客户端适配说明")' "$SETUP" \
|| fail "client selection must expose the third-party integration contract"
grep -Fq '查询:GET ?action=query' "$SETUP" \
|| fail "client integration guidance must document the query action"
grep -Fq '保存:GET ?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 for file in wloc.module wloc.sgmodule wloc.conf wloc.lpx wloc.stoverride; do
test -s "$MODULES/$file" || fail "missing bundled module: $file" test -s "$MODULES/$file" || fail "missing bundled module: $file"