This document shows the intended host-process integration patterns for
mobilebridge.
For the hosted Android fleet model built on top of these primitives, see device-farm.md.
The core helper is StartAttachedServer:
session, err := mobilebridge.StartAttachedServer(ctx, serial, "127.0.0.1:9222")
if err != nil {
return err
}
defer session.Close()It gives you an *AttachedServer with:
Serial: the Android device serialAddr: the local server listen addressEndpoint: the normalized public HTTP endpoint, e.g.http://127.0.0.1:9222Proxy: the underlying proxy instanceDone(): a channel closed when reconnect attempts are exhausted
For most host processes, session.Endpoint is the only value that
matters: it behaves like a local desktop Chrome browserURL.
List visible Android devices before allocating a session:
devices, err := mobilebridge.ListDevices(ctx)
if err != nil {
return err
}
for _, d := range devices {
fmt.Printf("%s %s %s\n", d.Serial, d.State, d.Model)
}If you need richer metadata, call Enrich on selected devices:
if len(devices) > 0 {
_ = devices[0].Enrich(ctx)
}The recommended host-process shape is:
- resolve the target Android device
- start an attached server on a local loopback port
- hand
session.Endpointto your CDP client - close the session when the client is done
Example:
ctx := context.Background()
session, err := mobilebridge.StartAttachedServer(ctx, "R58N12ABCDE", "127.0.0.1:9222")
if err != nil {
log.Fatal(err)
}
defer session.Close()
browser, err := puppeteer.Connect(puppeteer.ConnectOptions{
BrowserURL: session.Endpoint,
})
if err != nil {
log.Fatal(err)
}
defer browser.Close()If your host already owns the ADB forward port allocation, use
StartAttachedServerWithADBPort.
VulpineOS integrates mobilebridge through its public
internal/extensions.MobileBridge interface.
The current adapter does two things:
ListDevices(ctx)mapsmobilebridge.Deviceinto the genericextensions.MobileDeviceshapeConnect(ctx, udid)startsStartAttachedServer, stores a cleanup callback, and returns a genericMobileSession{CDPEndpoint: ...}
That keeps mobilebridge public and Android-specific, while letting
VulpineOS treat Android and iOS bridges through one generic surface.
Conceptually:
devices, _ := extensions.Registry.Mobile().ListDevices(ctx)
session, _ := extensions.Registry.Mobile().Connect(ctx, devices[0].UDID)
defer extensions.Registry.Mobile().Disconnect(ctx, session.ID)
fmt.Println(session.CDPEndpoint)The returned CDPEndpoint can then be consumed by the same automation
layer that drives desktop browsers.
The paid API should treat mobilebridge as a session provider, not as
an endpoint implementation detail.
Recommended service shape:
- allocate or choose an Android device
- call
StartAttachedServerin the worker process, or expose the hosted worker-control API when the worker is managed remotely - store the resulting
session.Endpointwith the job/session record - hand that endpoint to the browser automation worker
- close the attached server on job completion or worker shutdown
In other words, the API owns:
- job/session lifecycle
- worker placement
- billing and auth
- persistence
while mobilebridge owns only:
- ADB device discovery
- devtools socket resolution
- local CDP proxying
- reconnect behavior
- gesture extensions
When the worker process is not the same process as the control plane,
mobilebridge can expose a narrow HTTP control API instead:
mobilebridge --worker-control 127.0.0.1:7788Current endpoints:
POST /sessionswith{ "device_id": "..." }DELETE /sessions/{id}POST /sessions/{id}/targetsPOST /sessions/{id}/recording/startPOST /sessions/{id}/recording/stopGET /recordings/{id}/contentDELETE /recordings/{id}GET /health
This lets a control plane such as vulpine-api keep session leases,
tenant auth, and placement logic in one place while the worker host owns
the actual ADB attach and local CDP bridge lifecycle.
When the worker also receives:
--worker-heartbeat-url--worker-id--worker-token--worker-control-token--worker-advertise-url
it can self-register directly with the control plane. The published
heartbeat includes current device inventory plus worker load fields such
as active_sessions, queue_depth, max_sessions, failure_rate, and
last_error.
When --worker-control-token is set, the worker-control mutation routes,
recording downloads, and recording deletes require Authorization: Bearer ... from the control plane. That keeps attach, release, target
creation, recording control, and artifact cleanup off unauthenticated
private-network surfaces.
When embedding mobilebridge, check these cases explicitly:
ErrADBMissingErrDeviceNotFoundErrNoDevtoolsSocketErrBusy
Operationally:
- treat
Done()closing as permanent session loss - for transient drops, let the built-in reconnect path recover first
- always
Close()the attached server to remove forwards and stop the local HTTP server
Use NewProxy + NewServer directly only when you need custom process
ownership or a nonstandard networking shape.
For almost all integrations:
- use
StartAttachedServer - consume
session.Endpoint - close the session when done