mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-06 08:07:53 +02:00
945600428e
Closes two layers of the device-control gap:
L1 — Mac daemon's tunnelProvider is now real, not a stub. New files:
- ios-qa/daemon/src/devicectl.ts: thin wrappers around `xcrun devicectl`
(list, info, launch, install, copy-from) with spawn+resolve injection
for unit testability.
- ios-qa/daemon/src/tunnel-bootstrap.ts: orchestrates find-device →
launch-app → resolve IPv6 → wait-for-healthz → copy-boot-token →
POST /auth/rotate → return DeviceTunnel with rotated bearer.
- ios-qa/daemon/test/tunnel-bootstrap.test.ts: 7 tests covering every
error branch (no_devices, no_paired_device, device_locked,
state_server_unreachable, resolve_failed, happy path, explicit-udid).
- index.ts wired to use bootstrapTunnel() when running as CLI; tests
keep using injected stubs.
L2 — In-process touch synthesis for non-UIControl widgets. New target
in the fixture SPM package:
- DebugBridgeTouch (Objective-C): KIF-derived UITouch + IOHIDEvent
synthesis. Loads IOKit dynamically via dlopen/dlsym (IOKit is a
private framework on iOS, can't link statically). Uses iOS 18+
_UIHitTestContext for SwiftUI hit-testing. Public Swift-callable
API: DebugBridgeTouch.sendTap(at:in:). MIT-attributed to
kif-framework/KIF.
- DebugBridgeUI/Bridges.swift: rewritten MutationBridge.handleTap to
delegate to DebugBridgeTouch. ScreenshotBridge + ElementsBridge
implementations also land here.
- FixtureApp/Sources/FixtureApp/FixtureAppApp.swift: wires the bridges
on app launch under #if DEBUG.
Real-iPhone evidence (Conductor sandbox → CoreDevice IPv6 → live app):
- /healthz returns 200 with on-device JSON body
- /screenshot returns 427KB PNG that decodes to your actual phone screen
- Boot-token rotation kills the original token (401 boot_token_invalid
on reuse — the load-bearing security property verified live)
- Session lock + auth gate (401/423/200 paths all work)
- Schema-versioned state envelope (_schema_version + _accessor_hash)
Known partial: synthesized UITouch reaches SwiftUI's host view per
device-side syslog ("non-local connection from fd...:2" earlier showed
the per-connection peer gate working), and HTTP returns 200 ok:true,
but SwiftUI Button onTap handler doesn't fire. UIControl widgets DO
work via UIControl.sendActions. Next step is attaching lldb to the
live app on device to diagnose which validation SwiftUI's gesture
recognizer is failing. The architectural primary path
(`POST /state/<key>` to mutate @Snapshotable fields) is unaffected
and is the recommended control vector.
Documented sources for the KIF-derived synthesis:
- https://github.com/kif-framework/KIF (MIT)
- UITouch-KIFAdditions.m: init flow with _setLocationInWindow:,
setGestureView:, _setIsFirstTouchForView:
- IOHIDEvent+KIF.m: digitizer event construction
- iOS 18+ _UIHitTestContext path for SwiftUI hit-testing
61 lines
1.6 KiB
Swift
61 lines
1.6 KiB
Swift
// FixtureApp — minimal SwiftUI app used by the ios-qa device-path E2E test.
|
|
//
|
|
// On launch:
|
|
// 1. Boot StateServer (loopback :::1/127.0.0.1 + 9999)
|
|
// 2. Log boot token to os_log so devicectl + the Mac daemon can scrape it
|
|
// 3. Render a single ContentView so the app stays foreground
|
|
//
|
|
// Everything ios-qa-related is gated #if DEBUG. Release builds compile this
|
|
// to a no-op app (no StateServer, no DebugBridge import, no overlay).
|
|
|
|
import SwiftUI
|
|
|
|
#if DEBUG
|
|
import DebugBridgeCore
|
|
#endif
|
|
|
|
#if DEBUG && canImport(UIKit)
|
|
import DebugBridgeUI
|
|
#endif
|
|
|
|
@main
|
|
struct FixtureAppApp: App {
|
|
init() {
|
|
#if DEBUG
|
|
StateServer.shared.start()
|
|
// Wire the three UIKit-backed bridges so /screenshot, /elements,
|
|
// /tap, /type, /swipe actually do something on the device.
|
|
#if canImport(UIKit)
|
|
DebugBridgeUIWiring.installAll()
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ContentView()
|
|
}
|
|
}
|
|
}
|
|
|
|
struct ContentView: View {
|
|
@State private var counter: Int = 0
|
|
|
|
var body: some View {
|
|
VStack(spacing: 24) {
|
|
Text("ios-qa fixture")
|
|
.font(.largeTitle.bold())
|
|
Text("StateServer should be on :9999")
|
|
.font(.subheadline)
|
|
.foregroundColor(.secondary)
|
|
Button("Tap (\(counter))") {
|
|
counter += 1
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.accessibilityIdentifier("tap-button")
|
|
}
|
|
.padding()
|
|
.accessibilityIdentifier("fixture-content")
|
|
}
|
|
}
|