control_url and
continue the session.
The Python and Go mobile drivers implement control reconnect, cursor resume,
handshake replay, and idempotent input resends. Follow this page when you
manage a raw control WebSocket yourself. See
Telemetry reliability for the separate read-only
stream.
Classify the close
Use the socket close code to decide whether to retry:
Use exponential backoff with jitter and a bounded attempt budget. On a refused
WebSocket upgrade,
401 means the credential is invalid and 403 means the
session is no longer active. Both are terminal for that URL.
The session-scoped control URL remains valid for reconnects while its
allocation is active. Do not mint a different control URL during a retry.
Opt in to cursor checkpoints
Addresume=1 to the URL on the first connection. The server then sends an
opaque checkpoint after each delivered batch:
resume=1, the server sends no cursor checkpoints. A fresh control
connection starts at the live tail.
Present the cursor on reconnect
Reconnect to the original URL. Parse it with a standard URL query API, preserve its existing parameters, and set or replaceresume=1 and cursor with the
last completed checkpoint. Pass the opaque cursor unchanged as the parameter
value so the API encodes only that value. Never concatenate it into the URL or
encode the complete URL:
Handle an expired replay window
The control replay window is bounded. It retains roughly two minutes and about 100 frames. When the cursor predates the retained window, the server sends a resync signal as the first stream-facing frame and then continues from the live tail:Screen.observe again before deciding the next action.
Resend input exactly once
Cursor resume protects frames sent to your client. A connection can still drop after you send an input but before its response arrives. At that point, you do not know whether the phone executed it. Give every logicalTouch.*, Keyboard.*, and input Locator.* command
(tap, fill, press) a client-generated idempotencyKey:
id and the
same idempotencyKey. The session records the first response and returns it to
the duplicate without executing the input again.
Do not reuse an idempotency key for a different command. Locator.tap,
Locator.fill, and Locator.press take a key like the Touch and Keyboard
inputs. Read operations such as Screen.observe, Screen.screenshot,
Device.info, and the other Locator methods do not need one.
Keep request IDs monotonic
Never reuse a DCP requestid during the lifetime of your client. A resumed
connection can replay a response created before the drop. Reusing its id
could attach that old response to new work.
Continue one monotonically increasing request counter across reconnects. Match
each response by id, ignore id-less notifications after handling them, and
discard a stale response whose id belongs to an already-resolved request.
Repeat the handshake
Capability state belongs to a connection. After every reconnect, sendProtocol.handshake and
process its response before sending more work. Re-evaluate capabilities
instead of assuming they match the prior socket.
The server sends WebSocket pings for keepalive. Standard WebSocket libraries
answer them automatically, so you do not need an application-level DCP ping.
Raw client checklist
- Reconnect to the original session URL with the last completed cursor.
- Process cursor notifications and persist only completed checkpoints.
- On
1001,1011,1013, or abrupt loss, retry with bounded jittered backoff and the last cursor. - Stop on a terminal close or a
401/403upgrade refusal. - If the server requests a resync, rebuild device state.
- Keep DCP request IDs monotonic across sockets.
- Repeat
Protocol.handshakeafter each reconnect. - Reuse the same idempotency key only when resending the same ambiguous input.
- Tolerate replayed DCP frames because cursor resume is at least once.
Next steps
Drive a phone with raw DCP
Open the control socket and send your first command.
DCP message reference
Review methods, parameters, results, and errors.
Telemetry reliability
Resume the read-only stream and refill a Telemetry gap.