feat(ios,android): P2 wave, git panel, token UX, per-host WS token, docs
App layer, four sequential slices (a shared .xcodeproj means adding files regenerates it, so these could not run in parallel): - token UX end to end: pairing prompts for a token when a host 401s, POST /auth validates it, and 204-without-Set-Cookie is correctly read as "this server has auth disabled" rather than "authenticated". A host paired before the token was turned on recovers by re-pairing in place. Remove-host now exists and finally gives PushRegistrar.handleHostRemoved a caller. - project git panel + worktree lifecycle (T-iOS-32) + claude --resume history — the parity gap with Android and the web front end. - terminal search (T-iOS-33) and voice PTT (T-iOS-31) with an epoch guard so a session switch between dictation and confirm cannot inject into the wrong session. - theme + Dynamic Type (T-iOS-34) and web ?join= interop (T-iOS-35). RootView no longer hard-locks .preferredColorScheme(.dark). Also unpins SwiftTerm to 1.15.0 by dropping the local hasActiveSelection that collided with the upstream one, verified green from a fresh derivedDataPath. Includes the two HIGH fixes the security review found: - iOS resolved the WS token host-independently, so a token-gated host sitting next to an open one could never open a terminal and no on-screen remedy could fix it. Now one transport per host; cross-host leakage is structurally impossible since both read paths return only that host's own value. - Android reported the host's own git-credential 401 (git-ops.ts:108, "Push authentication required on the host.") as "your access token is wrong", because a blanket 401 mapping ran ahead of the per-route one. Git-write routes are now ROUTE_DEFINED and keep the server's message. And the doc sync: README/ios README no longer claim the client is unmerged on feat/ios-client, the Clients section finally lists Android, and the plan checkboxes reflect what is actually built. iOS 534 app tests + 452 package tests; Android 687 tests.
This commit is contained in:
@@ -57,6 +57,19 @@ final class TerminalViewModel {
|
||||
static let replayTooLargeMessage =
|
||||
"服务器 scrollback 超过客户端上限,请调低 SCROLLBACK_BYTES 或调高客户端上限"
|
||||
|
||||
/// Actionable copy for `.failed(.unauthorized)` (C1 over B3, ios-completion
|
||||
/// §1.1). The server answers **401 on the WS upgrade for two different
|
||||
/// reasons** — the Origin/CSWSH check runs first, the `webterm_auth` cookie
|
||||
/// second (src/server.ts:1367-1379) — and the status is identical, so the
|
||||
/// client provably cannot tell them apart. Hence the copy names BOTH
|
||||
/// remedies instead of guessing one. Retrying is pointless (the engine
|
||||
/// already treats this as terminal), so the message asks for a credential
|
||||
/// change, not patience.
|
||||
static let unauthorizedMessage =
|
||||
"主机拒绝了连接(401):访问令牌缺失或不正确,也可能是主机的 ALLOWED_ORIGINS 不含本 App 拨号的地址。"
|
||||
+ "请在会话列表的主机菜单里「管理主机与令牌」重新输入访问令牌;"
|
||||
+ "若令牌无误,就把主机的 ALLOWED_ORIGINS 设成 App 连接的完整地址后重启 web-terminal。"
|
||||
|
||||
/// Last VALID dims forwarded to the engine (SwiftTerm `sizeChanged`).
|
||||
/// Read by the wiring layer for `notifyForegrounded(dims:)` — the frozen
|
||||
/// §3.2 signature needs real cols/rows and this is their single source.
|
||||
@@ -271,6 +284,11 @@ final class TerminalViewModel {
|
||||
case .failed(.replayTooLarge):
|
||||
banner = .none
|
||||
phase = .failed(message: Self.replayTooLargeMessage)
|
||||
case .failed(.unauthorized):
|
||||
// Same shape as replayTooLarge: a terminal state, so the spinner
|
||||
// must go and the terminal becomes read-only with actionable copy.
|
||||
banner = .none
|
||||
phase = .failed(message: Self.unauthorizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user