Skip to content

feat(restapi): ephemeral loopback port by default + discovery file - #825

Open
danielpaulus wants to merge 34 commits into
mainfrom
feat/restapi-ephemeral-port
Open

feat(restapi): ephemeral loopback port by default + discovery file#825
danielpaulus wants to merge 34 commits into
mainfrom
feat/restapi-ephemeral-port

Conversation

@danielpaulus

Copy link
Copy Markdown
Owner

Why

The go-ios REST daemon defaulted to :8080, which meant it squatted on a fixed, well-known port on all interfaces — colliding with the many dev tools that also grab 8080, and binding more broadly than a local automation daemon should. There was also no way for an SDK to know where the daemon was listening.

This changes the default to an ephemeral, loopback-only port and publishes a discovery file so SDKs (TS/Python/Java/C#) can auto-find the running daemon regardless of which port it landed on. It also resolves the earlier SDK-default vs daemon-default port mismatch: the SDK default becomes discovery, not a hardcoded port.

What

  • Default --addr :8080127.0.0.1:0 (ephemeral, loopback-only). --addr still works to pin/expose (:8080, 0.0.0.0:9000, …).
  • Replace srv.ListenAndServe() / ListenAndServeTLS with an explicit net.Listen("tcp", cfg.addr) (and tls.NewListener for TLS) so the OS-assigned port is knowable. The real host:port is read from ln.Addr().(*net.TCPAddr) and that real address is logged (previously it logged cfg.addr, which would be the useless 127.0.0.1:0). Then srv.Serve(ln).
  • Discovery file written after a successful bind, then removed on graceful shutdown (existing SIGINT/SIGTERM + srv.Shutdown path) and on serve failure.
  • New restapi/api/discovery.go centralizes home-dir resolution + atomic write/remove so the format is documented and testable. New log lines use golog with module=go-ios/restapi.
  • --disable-auth, rate-limit flags, streaming timeouts, swagger gating: unchanged.

Discovery contract (implemented exactly)

  • Home dir: GO_IOS_HOME if set and non-empty, else ~/.go-ios (created 0700 if missing).
  • File: <home>/rest-api.json, mode 0600, atomic temp+rename.
  • JSON: { "baseUrl", "host", "port", "pid", "startedAt", "tls" }baseUrl authoritative (scheme+host+port; https when TLS).

Tests (device-free)

restapi/api/discovery_test.go + an updated parseServerConfig default assertion:

  • default --addr is 127.0.0.1:0; --addr override still works;
  • discovery file written with the actual bound port (a real ephemeral net.Listen is bound in the test), correct JSON shape/keys, 0600 perms;
  • honors GO_IOS_HOME, creates the home dir, atomic overwrite leaves no temp files, tlshttps scheme;
  • removed on shutdown (idempotent).

go test ./restapi/api/... and -race green; go vet and gofmt -l clean. (The module-root ./restapi/... build needs the gitignored swag docs — out of scope.)

Stacking

Stacked on the daemon PRs #817 / #821 (feature/restapi-complete). It should merge with/after #821.

🤖 Generated with Claude Code

danielpaulus and others added 30 commits August 10, 2026 09:58
…ts, no-panic marshal)

Foundational hardening for running the REST API in production. No endpoint
behavior changes; the auth model from #792 (GO_IOS_API_KEY / --disable-auth)
is preserved.

- Serve via an explicit http.Server with graceful shutdown on SIGINT/SIGTERM
  (drains in-flight requests, 10s timeout).
- Bound abuse without breaking streams: ReadHeaderTimeout, IdleTimeout,
  MaxHeaderBytes. Deliberately no WriteTimeout so /syslog,/listen,/ostrace,
  /notifications can stream indefinitely.
- Optional TLS via --tls-cert/--tls-key; configurable bind via --addr
  (default :8080). Flags parsed in the same tolerant FlagSet as --disable-auth.
- Unauthenticated /healthz and /readyz probes outside /api/v1.
- Gate the swagger UI behind auth (served under /api/v1) when a token is set.
- MustMarshal no longer panics on an unmarshalable value — it returns a JSON
  error envelope, so a stream/handler can't be crashed by it.
- Add RespondError helper for a consistent {"error":...} envelope.

Tests: MustMarshal no-panic + valid-unchanged, health endpoints, RespondError,
parseServerConfig (defaults, flags, tolerates unknown args). go build/vet/test
./restapi/... green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
…s, mobilegestalt, ps, lockdown)

First batch of CLI-parity endpoints, all read-only, each mirroring the exact
go-ios library call the corresponding `ios` CLI command uses:

- GET /device/:udid/devicename    -> ios.GetValues (ios devicename)
- GET /device/:udid/date          -> ios.GetValues (ios date)
- GET /device/:udid/battery       -> ios.GetBatteryDiagnostics (ios batterycheck)
- GET /device/:udid/diagnostics   -> diagnostics.AllValues (ios diagnostics list)
- GET /device/:udid/mobilegestalt -> diagnostics.MobileGestaltQuery (ios mobilegestalt), keys via ?key=
- GET /device/:udid/processes     -> instruments.ProcessList (ios ps), ?apps=true filters apps
- GET /device/:udid/lockdown      -> ios.GetValues (ios lockdown get)

Handlers surface library errors as {"error":...} via RespondError (no discarded
errors, no panics). Wired through registerDeviceInfoRoutes in routes.go.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
…e, lang, memlimitoff)

Mirrors the ios CLI: diagnostics.Reboot/Shutdown, mcinstall.Erase (gated by
?confirm=true), amfi.EnableDeveloperMode + imagemounter.IsDevModeEnabled +
amfi.RevealDevMode, ios.Get/SetLanguage, and instruments ProcessControl
DisableMemoryLimit. Errors surfaced via RespondError; no panics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
…h, ios crash ls/rm)

Uses the iOS 17+ file service and streams pull/push through the HTTP body, so
there is no caller-supplied host path and no host-side traversal. Domains:
app|app-group|crash|temp. Crash reports: list + remove (crash-log downloads are
available via /files?domain=crash).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
- GET/PUT /wallpaper: springboard GetHomeScreenWallpaperPNG (image/png); set via
  multipart (image+p12 supervisor identity+screen) -> mcinstall.SetWallpaperSupervised.
- GET/PUT /icon-layout: springboard Get/SetIconLayout.
- GET/PUT /pasteboard: pasteboard Get/SetText (iOS 17+).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
…age list/unmount)

- POST /profiles: mcinstall AddProfile / AddProfileSupervised (multipart p12); DELETE /profiles/:name.
- GET /image/list: mounted image signatures (hex); DELETE /image: imagemounter.UnmountImage.
GET /profiles and GET/PUT /image already existed; only the missing verbs added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
- GET/PUT /assistivetouch: ios.Get/SetAssistiveTouch.
- GET/PUT /timeformat: ios.Get/SetUses24HourClock.
- PUT/DELETE /wifi: mcinstall.PrepareWifi/RemoveWifi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
GET /sysmontap streams instruments Sysmontap CPU-usage samples (matches the
existing syslog/listen streaming pattern). pcap is deferred until ios/pcap
exposes a packet-callback streaming API (it currently writes a local file).
…, clear-passcode, clear-screen-time-password)

All under /device/:udid/mdm, POST multipart with a p12 supervisor identity +
password (escalated mcinstall session via conn.Escalate). Credentials stay in
memory, never logged or persisted. clear-passcode also takes a base64 token.
…streamed logs

Long-running device operations now run as background jobs:
- POST /jobs/runtest, /jobs/runwda, /jobs/forward -> 202 + job id
- GET  /jobs (per-device), GET /jobs/:id (status), DELETE /jobs/:id (stop)
- GET  /jobs/:id/logs streams that job's isolated log (history + live tail)

Each job captures its output on a dedicated jobLog sink (io.Writer wired into the
testmanagerd TestListener), so concurrent jobs never interleave. Lifecycle events
are logged via ios/golog with module=go-ios/restapi + udid + job attrs. Terminal
state is immutable, so stopping a job isn't relabeled as a failure when its
context-cancelled goroutine returns. In-memory job manager + jobLog are unit-tested
(incl. -race): lifecycle, stop-is-terminal, per-device isolation, log stream/close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJXoR6KAaEXEqzyJien7WJ
PUT /httpproxy (supervised, multipart host/port/p12/user/pass/password) ->
mcinstall.SetHttpProxy; DELETE /httpproxy -> mcinstall.RemoveProxy.
Agent-level, not device-scoped, so they live at /api/v1 (behind auth):
- GET    /tunnels               list running tunnels
- DELETE /tunnels/:udid         stop a device tunnel
- POST   /tunnels/:udid/refresh refresh a device tunnel
- POST   /tunnel-agent/shutdown stop the tunnel agent
They query the running agent via ios.HttpApiHost/HttpApiPort. 'tunnel start' is
not exposed (privileged long-running daemon). Added a route-registration smoke
test that builds the full tree so gin route conflicts fail loudly.
httptest-based tests exercising the request-validation branches (which run before
any device I/O): missing/invalid params across files, mobilegestalt, wifi, mdm,
crashes, devmode, and the job endpoints (erase confirm-gate, missing bundle/ports,
job-not-found 404). Closes the biggest coverage gap for the parity endpoints.
Add RateLimitUDID: a token-bucket (golang.org/x/time/rate) gin middleware keyed
by device UDID, applied across the /device/:udid group. Requests over the limit
get 429; each UDID has its own bucket so devices don't throttle each other.
Configurable via --rate-limit (req/s, default 20) and --rate-burst (default 40);
0 disables. Uses the atomic sync.Map LoadOrStore pattern (no create race).

Tests (device-free, -race): burst-then-429, disabled-when-zero, per-device
isolation, and a concurrent-hammer test asserting the shared bucket isn't
exceeded under load.
Adversarial review follow-ups on the 56-endpoint REST parity work:

Security/correctness fixes:
- Bound in-memory uploads: readFormFile and the raw-body reads in
  SetPasteboard and AddProfile now go through readAllLimited (256 MiB cap)
  so an authenticated client can't OOM the daemon with an oversized
  multipart file or body. PushFile already streams (Content-Length gated)
  and is unaffected.
- Fix a lost-line gap in GET /jobs/:id/logs: snapshot() then subscribe()
  raced, dropping any line written in between. Added jobLog.
  snapshotAndSubscribe() which takes the backlog and the live subscription
  under one lock.
- Bound the process-wide job registry: DELETE /jobs/:id on an already
  terminal job now purges it (jobManager.remove, terminal-only) so finished
  jobs' buffered logs don't accumulate forever. Running jobs are still
  stopped, never silently dropped.

Tests (httptest + in-context device):
- Auth coverage: BearerAuth accept/reject, and a tree-walk asserting all
  80 registered /api/v1 routes return 401 unauthenticated.
- Files: Content-Disposition base-name sanitisation (traversal-y remote
  can't inject a host path), ls/pull/push validation, push 411 without
  Content-Length.
- Upload limits: readAllLimited boundaries + oversized pasteboard body.
- Proxy/MDM multipart validation.
- Jobs: full HTTP lifecycle (create/list/get/stop/delete), per-device
  isolation (cross-udid GET/DELETE 404 and no stop), remove-terminal-only,
  atomic snapshot+subscribe.

go build/vet/test ./restapi/... green incl. -race; gofmt clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Make the REST daemon match the TypeSpec-authored OpenAPI contract the
official SDKs are generated from.

Real SSE framing (event:/data:/blank-line) on all six streaming
endpoints, with a periodic heartbeat on idle, replacing the previous
NDJSON/concatenated-JSON writes:
  - /syslog       -> event "syslog"       (SyslogMessage)
  - /notifications-> event "appstate"     (AppStateNotification)
  - /ostrace      -> event "ostrace"      (OsTraceEntry)
  - /listen       -> event "attachdetach" (AttachDetachEvent)
  - /sysmontap    -> event "sample"       (CpuUsageSample)
  - /jobs/{id}/logs-> event "log"         (JobLogLine)
  - all           -> event "heartbeat"    ({}) on idle

Payload models use the spec's camelCase field names; a shared streamSSE
helper drives frames + heartbeats and flushes after each write.

Other spec conformance:
  - setlocation: longtitude -> longitude (query param, checks, messages,
    swagger annotations)
  - screenshot: content-type image/png (was application/octet-stream)
  - streaming error paths now use the GenericResponse envelope

Device-free unit tests cover the SSE framing + heartbeats, payload
mappers, longitude, and the removed misspelled param.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Add device diagnostics and network endpoints under /device/:udid:
- GET /diskspace       -> afc.(*Client).DeviceInfo (filesystem info)
- GET /ip              -> pcap.FindIp (MAC/IPv4/IPv6)
- GET /rsd             -> device.Rsd.GetServices (RSD service list; 400 if no tunnel)
- GET /battery/registry-> diagnostics.(*Connection).Battery (IORegistry stats)

Extend the existing GET /lockdown handler to accept an optional ?domain=
query param, returning domain-scoped values via GetValueForDomain.

Handlers live in a new diagnostics_net_endpoints.go with device-free unit
tests for the RSD capability/error paths and route registration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Expose the non-interactive `ios webinspector` operations over the REST API
under /device/:udid/webinspector:

- GET  /pages  -> list inspectable pages (client.ListPages)
- POST /launch -> open a URL via a remote automation session
  (OpenApp + AutomationSession + Start + Navigate)
- POST /eval   -> evaluate JS in a page (client.Evaluate)

The interactive commands (js-shell, cdp) are intentionally not exposed.
"Web Inspector / Remote Automation not enabled" conditions map to 424;
missing url/script and a non-existent page map to 4xx.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Extract the private WDA/DeviceKit UI-automation HTTP client that lived in
cmd_ui.go into a new, exported ios/uidriver package so the CLI and the
upcoming REST ui endpoints can share one driver.

uidriver.Driver is constructed against a backend base URL (the forwarded
WDA :8100 / DeviceKit :12004 address) and exposes Tap/Swipe/LongPress/
Type/PressButton/Screenshot/Source/WindowSize/Orientation/SetOrientation/
AppLaunch/AppTerminate/AppForeground/Status/API/Stream. Methods return
values and errors instead of calling os.Exit, so the package is safe to
embed. Request/response types are exported and JSON-tagged.

cmd_ui.go keeps arg parsing, backend resolution (wda/devicekit/auto) and
output formatting, delegating all HTTP work to the driver. CLI behavior is
unchanged. Adds ios/uidriver unit tests driving the client against an
httptest backend (methods/paths/bodies for every action plus the chunked
streaming helper).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Adds the AFC-based (com.apple.afc) filesystem surface under
/device/:udid/fsync (ls, tree, pull, push, rm, mkdir), scoped to the
media directory or, with ?bundleID=, to an app data container via
house_arrest -- mirroring 'ios fsync'. Distinct from the iOS 17+
fileservice /files surface.

Also adds provisioning parity: GET /device/:udid/cloudconfig
(mcinstall GetCloudConfiguration) and the device-free fleet route
GET /prepare/skip-options (mcinstall GetAllSetupSkipOptions).

Caller-supplied device paths are validated by safeDevicePath, which
rejects any '..' element so a request cannot traverse out of the AFC
root; pull additionally relies on the afc package's own entry-name
filtering. Uploads stream to the device and are bounded by
maxUploadBytes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Add device REST endpoints for accessibility + location parity:
- GET/PUT /device/:udid/voiceover  -> ios.GetVoiceOver / ios.SetVoiceOver
- GET/PUT /device/:udid/zoom       -> ios.GetZoomTouch / ios.SetZoomTouch
- POST    /device/:udid/ax/audit   -> accessibility RunAudit (bounded timeout)
- GET     /device/:udid/ax         -> accessibility element snapshot (no listeners)
- PUT     /device/:udid/setlocation/gpx -> simlocation.SetLocationGPX (multipart)

Set handlers accept enabled via JSON body or query param. GPX upload is
size-bounded via readFormFile, written to a temp file and cleaned up.
Device-free tests cover validation/error paths, enabled parsing precedence,
audit timeout validation, and GPX multipart parsing/temp handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…ure/restapi-complete

# Conflicts:
#	restapi/api/errors.go
…ture/restapi-complete

# Conflicts:
#	restapi/api/errors.go
#	restapi/api/routes.go
…nto feature/restapi-complete

# Conflicts:
#	restapi/api/routes.go
Add /api/v1/device/:udid/ui endpoints that drive on-device UI automation
through the ios/uidriver package against a running, forwarded WebDriverAgent
(or DeviceKit) backend.

Endpoints (all under /device/:udid/ui):
  POST tap, swipe, longpress, type, button
  GET  screenshot (image/png), source, size, orientation, status
  PUT  orientation
  POST app/launch, app/terminate, app/foreground
  POST api (raw uidriver.API passthrough)

Backend is selected per request via ?backend=wda|devicekit and addressed via
?wdaUrl=<url> (defaults 127.0.0.1:8100 / :12004), with an optional ?timeout.
A fresh uidriver.Driver is built per request. uidriver errors are mapped:
*HTTPError and transport/unreachable failures -> 502, unsupported actions
(button/foreground/stream) -> 501, bad input -> 400.

Tests stand up an httptest server mimicking WDA and assert each endpoint
forwards the correct method/path/body and returns the mapped response,
including unreachable-backend -> 502 and bad-input -> 400. Fully device-free.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…terface

Add host-local /api/v1/sign/{certificate,provision,app} and prepare routes
(/prepare/create-cert host, /device/:udid/prepare) to the daemon. Endpoints
depend only on a new Signer interface (restapi/api/signing_adapter.go); the
default signingAdapter wraps ios/signing and is swappable for the future
in-repo ios/codesign impl with no endpoint/test changes.

Uploads are bounded by maxUploadBytes and written to a per-request temp dir
that is always cleaned up; generated P12/profile artifacts are read then
removed from disk. No key bytes or passwords are ever logged.

Tests inject a mock Signer to verify multipart parsing, temp-dir write +
cleanup, correct Signer args, secret redaction, and artifact streaming —
fully device-free and impl-free — plus a compile-time Signer assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
danielpaulus and others added 4 commits August 10, 2026 22:19
Refactor ios/pcap to a context-aware streaming core: new
Stream(ctx, device, io.Writer) writes a valid pcap stream (global header +
per-packet records) until ctx is canceled, then returns cleanly by closing
the device connection to unblock the read (no goroutine leak). The existing
Start(device) CLI entry point keeps its signature and is reimplemented on top
of Stream, so `ios pcap` and its caller are unchanged.

Add GET /device/:udid/pcap streaming the live capture as
application/vnd.tcpdump.pcap (chunked, flushed per write). Capture lifetime is
the request context plus an optional ?timeout= (seconds, default 60s, capped at
3600s); client disconnect or the deadline stops it. Registered in routes.go in
a delimited block.

Tests are device-free: ios/pcap capture is verified against a fake connection
feeding canned frames (valid pcap out, stop on cancel/deadline, error surfaced
when not canceled, single close); the endpoint covers timeout parse/validation
and route registration. Live capture bytes cannot be mocked at the endpoint
layer (pcap.Stream connects internally), so that behavior is covered in the
pcap package tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Add two streaming REST endpoints to the go-ios daemon:

- GET /device/:udid/ui/stream?codec=mjpeg|h264 proxies a forwarded
  WDA/DeviceKit UI video stream straight through to the client,
  preserving the backend's Content-Type and flushing frames until the
  client disconnects or the backend ends. Maps ErrStreamUnsupported->501
  and unreachable backends->502.
- GET /device/:udid/screenshot/stream serves a multipart/x-mixed-replace
  MJPEG stream of device screenshots via the instruments screenshot
  service, flushing each frame until the client disconnects.

uidriver: add StreamWithContentType (Stream now delegates to it) so the
proxy can forward the backend's Content-Type verbatim; existing Stream
signature and tests are unchanged.

instruments: extract the shared MJPEG core (pngToJPEG conversion and a
reusable, context-driven StreamJPEGFrames frame source) out of the
package-global screenshot loop. StartMJPEGStreamingServer and mjpegHandler
keep their prior behavior, now fanning a single StreamJPEGFrames source
out to registered consumers.

Tests are device-free: ui/stream is exercised against an httptest backend
(pipe-through, Content-Type preservation, codec/backend error mapping,
client-disconnect stop); the screenshot multipart framing core is tested
via an injected frame channel; pngToJPEG has round-trip coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
…tree-agent-a9c552bbe20d85f56

# Conflicts:
#	restapi/api/routes.go
Change the REST daemon's default --addr from :8080 to 127.0.0.1:0 so it no
longer squats on a fixed port (colliding with dev tools) and binds
loopback-only by default. --addr still pins/exposes (e.g. :8080, 0.0.0.0:9000).

Bind explicitly via net.Listen (tls.NewListener for TLS) so the OS-assigned
port is knowable, capture the real host:port from ln.Addr(), log the REAL
address, then srv.Serve(ln). After a successful bind, publish a discovery file
at <home>/rest-api.json (home = GO_IOS_HOME or ~/.go-ios) with
{baseUrl,host,port,pid,startedAt,tls}, mode 0600, atomic temp+rename. Remove it
on graceful shutdown (SIGINT/SIGTERM path) and on serve failure. This lets SDKs
auto-discover a locally running daemon regardless of the (ephemeral or pinned)
port.

Discovery helpers live in restapi/api/discovery.go; new log lines use golog with
module=go-ios/restapi.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Comment thread ios/uidriver/uidriver.go
return nil, "", fmt.Errorf("uidriver: failed creating stream request: %w", err)
}
golog.Info("opening ui stream", "module", logModule, "backend", string(d.backend), "url", rawURL, "udid", d.udid)
resp, err := d.httpClient.Do(req)
Comment thread ios/uidriver/uidriver.go
req.Header.Set("Content-Type", "application/json")
}
golog.Debug("ui request", "module", logModule, "backend", string(d.backend), "method", method, "endpoint", endpoint, "udid", d.udid)
resp, err := d.httpClient.Do(req)
}

func (f flushWriter) Write(p []byte) (int, error) {
n, err := f.w.Write(p)
if path == "" {
return nil, nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if rmErr := os.Remove(path); rmErr != nil {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants