Extending
Writing a provider
Plug a new kind of device into Holtdroid by implementing the provider and agent APIs.
Цей контент ще не доступний вашою мовою.
A provider owns devices: it starts a session on a free one, reports its
state, and stops it. Holtdroid ships provider-emulator (emulators as
Kubernetes Pods) and provider-fake (simulated devices for development).
Anything else — a farm of USB phones, emulators on bare metal, a cloud
device service — is a new provider. It is a plain HTTP service; the api only
needs its URL:
providers: - name: lab url: http://lab-provider.devices:8080What the api expects
Section titled “What the api expects”The endpoints are listed in Contracts;
the request and response shapes are the Go types in
internal/contract/types.go
and the OpenAPI document a Go provider serves at /openapi.json.
- The provider is the source of truth. The api is stateless: it asks the provider for the session list on every page load and reaper pass, and expects it to survive restarts of both. Keep session state where your devices are (Pod annotations, a file next to the phone, your service’s database), not in memory only.
GET /v1/capacitylists slots. One slot per device that can run a session,free,busy,offlineormaintenance. The api refuses a launch when no slot is free, so keep it honest and cheap: the reaper calls it.POST /v1/sessionsreturns quickly. Answer with the session inprovisioningand do the work in the background. The UI showsstateanddetailwhile it waits forready. Return 409 when there is no free slot, 400 for a bad request. WhenCreateSession.slotis set, use that slot: 422 if you don’t know it, 409 if it isn’t free. The api always sends the slot it picked from your capacity.- The agent downloads the build.
CreateSession.artifactUrlpoints at the api’s internal port and serves the exact cached revision. The device side must be able to reach it; the browser never does. WhenCreateSession.appDatais set, pass it to the agent too: it restoresappData.url(also on the internal port) after installing the APK. Reportcapabilities.webviewonly if your devices accept acom.android.webviewbuild signed with Chromium’s debug key and listcom.google.android.webviewas a WebView provider. In that case, passCreateSession.webviewto the agent, which installswebview.urlbefore the APK and switches towebview.packagewhen it is set (see WebView versions). POST /v1/sessions/{id}/touchstores activity. The api batches the last user activity and flushes it everysessions.flushInterval; return it aslastActivityso the idle timeout survives an api restart.DELETE /v1/sessions/{id}frees the device. Wipe what the user left behind before the slot isfreeagain. The reaper calls it for idle and expired sessions.agentUrlis where the api proxies the browser’s requests:/s/{provider}/{id}/…with the prefix stripped. It must only be reachable from the api (andtunnelUrlfrom the ssh-gateway).- Errors follow RFC 9457 problem JSON, like the rest of the API; 404 for an unknown session id.
- Maintenance holds belong to the provider.
PUT /v1/maintenanceholds a slot (or, withoutslot, every slot) or releases it. Keep the holds with your session state so they survive a restart, and report a held slot asmaintenanceonce nothing runs on it; a running session keeps going.contract.MaintenanceStatedoes the bookkeeping for Go providers. 400 for a slot you don’t know. GET /v1/inventoryis for the admins’ Inventory page. Return every slot (as incapacity), the session on it, and whatever you can tell about the device (device: model, Android, battery, sensors, memory, storage) or the machine (host: OS, CPU, load, sensors). Leave out what you don’t know and say why inerror; keep it within a few seconds, the page asks every 10.
capabilities tells the UI what to show: VPN locations and the default
one, whether DevTools and the clipboard work, the display kind (scrcpy),
and the ABIs the device runs, the one it runs natively first (the api
starts an APK with native code only on a device that lists one of its
ABIs, and the UI picks the build of a version whose ABI comes first). When devices differ from slot to slot, as
phones do, put a capabilities object (model, Android version, ABIs, root,
DevTools scope all or debuggable, ADB, app data) on each slot and copy
it onto the sessions that run there; the session page reads it from the
session.
The agent
Section titled “The agent”Every session needs an agent next to the device that implements the
agent API: status, the
scrcpy screen stream, DevTools targets and their WebSockets, clipboard,
network and device controls. The browser talks to it directly through the
api’s proxy, so whatever your device is, the UI works the same.
holtdroid agent starts and drives an emulator itself; its pieces in
internal/agent
(adb, Frida, scrcpy bridge, DevTools proxy) are the starting point for an
agent that drives some other device.
Writing it in Go
Section titled “Writing it in Go”Satisfy contract.Provider and mount it:
mux := http.NewServeMux()contract.RegisterProvider(contract.NewAPI(mux, "lab provider"), myProvider)http.ListenAndServe(":8080", mux)internal/provider/fake is the smallest complete example: slots, a session
lifecycle with delays, and an in-process agent. Test the round trip the way
internal/contract/contract_test.go does: serve your implementation with
httptest.NewServer and drive it through contract.NewProviderClient.
Any other language
Section titled “Any other language”Serve the same JSON over HTTP. The OpenAPI document of the Go types
(/openapi.json on any Go provider, for example
holtdroid provider-fake) can generate a server stub. To try it in the UI,
run holtdroid api with your provider in providers.
Observability
Section titled “Observability”Propagate the W3C traceparent header from the api’s requests into your
own spans, and the api’s traces continue through your provider. See
Monitoring and audit.