Skip to content

Commit 3c1fae0

Browse files
committed
Merge branch 'main' into fix-wda-context-was-getting-cancelled
2 parents ffcad54 + a8452ea commit 3c1fae0

File tree

8 files changed

+1055
-22
lines changed

8 files changed

+1055
-22
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
## [0.0.52](https://github.com/mobile-next/mobilecli/releases/tag/0.0.52) (2026-01-26)
1+
## [0.0.53](https://github.com/mobile-next/mobilecli/releases/tag/0.0.53) (2026-01-28)
22

3-
* General: Support for jsonrpc over websocket through /ws endpoint ([#154](https://github.com/mobile-next/mobilecli/pull/154))
43
* iOS: Fixed a bug where tcp forwarders were created on every request for mjpeg ([#158](https://github.com/mobile-next/mobilecli/pull/158))
54
* iOS: Lazily creating mjpeg tcp forwarder only when needed ([#158](https://github.com/mobile-next/mobilecli/pull/158))
65
* iOS: Multiple real devices would overwrite each other's wda tcp forwader ([#158](https://github.com/mobile-next/mobilecli/pull/158))
76
* iOS: Keep running WebdriverAgent xcuitest as long as "mobilecli server" is running ([#158](https://github.com/mobile-next/mobilecli/pull/158))
87
* iOS: Graceful shutdown and improved tunnel closing ([#158](https://github.com/mobile-next/mobilecli/pull/158))
98

9+
## [0.0.52](https://github.com/mobile-next/mobilecli/releases/tag/0.0.52) (2026-01-27)
10+
* General: Websocket support for jsonrpc at /ws, single connection multiple requests ([#154](https://github.com/mobile-next/mobilecli/pull/154))
11+
* iOS: Detect devicekit-ios process already running to Start Broadcasting a 2nd time ([#156](https://github.com/mobile-next/mobilecli/pull/156))
12+
* iOS: Return more elements in "dump ui", eg Button with no label ([#155](https://github.com/mobile-next/mobilecli/pull/155))
13+
1014
## [0.0.51](https://github.com/mobile-next/mobilecli/releases/tag/0.0.51) (2026-01-25)
1115

1216
* General: Plenty of code cleanups and refactoring for smaller and more maintained code ([#144](https://github.com/mobile-next/mobilecli/pull/144))

README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,16 @@ A universal command-line tool for managing iOS and Android devices, simulators,
2626
</a>
2727
</p>
2828

29-
3029
## Features 🚀
3130

3231
- **Device Management**: List, manage, interactive with connected mobile devices
3332
- **Cross-Platform Support**: Works with iOS physical devices, iOS simulators, Android devices, and Android emulators
3433
- **Emulator/Simulator Control**: Boot and shutdown emulators and simulators programmatically
3534
- **Screenshot Capture**: Take screenshots from any connected device with format options
3635
- **Multiple Output Formats**: Save screenshots as PNG or JPEG with quality control
37-
- **Screencapture video streaming**: Stream mjpeg video directly from device
36+
- **Screencapture video streaming**: Stream mjpeg/h264 video directly from device
3837
- **Device Control**: Reboot devices, tap screen coordinates, press hardware buttons
39-
- **App management**: Launch app, terminate apps. Install and uninstall coming next ⏭️
38+
- **App Management**: Launch app, terminate apps, install and uninstall
4039

4140
## Installation 📦
4241

@@ -178,6 +177,36 @@ mobilecli io text --device <device-id> 'hello world'
178177
- `VOLUME_UP`, `VOLUME_DOWN` - Volume up and down
179178
- `DPAD_UP`, `DPAD_DOWN`, `DPAD_LEFT`, `DPAD_RIGHT`, `DPAD_CENTER` - D-pad controls (Android only)
180179

180+
## HTTP API 🔌
181+
182+
***mobilecli*** provides an http interface for all the functionality that is available through command line. As a matter of fact, it is preferable to
183+
use mobilecli as a webserver, so it can cache and keep tunnels alive, speeding up your interactions with the mobile device or simulator/emulator.
184+
185+
```bash
186+
# Start the server (default port 12000)
187+
mobile server start
188+
189+
curl http://localhost:12000/rpc -XPOST -d '{"jsonrpc":"2.0", "id": 1, "method": "devices", "params": {}}'
190+
curl http://localhost:12000/rpc -XPOST -d '{"jsonrpc":"2.0", "id": 1, "method": "screenshot", "params": {"deviceId": "your-device-id"}}'
191+
192+
## WebSocket Support 🔌
193+
194+
***mobilecli*** includes a WebSocket server that allows multiple requests over a single connection using the same JSON-RPC 2.0 format as the HTTP API.
195+
196+
```bash
197+
# Start the server (default port 12000)
198+
mobilecli server start
199+
200+
# Connect and send requests using wscat
201+
wscat -c ws://localhost:12000/ws
202+
> {"jsonrpc":"2.0","id":1,"method":"devices","params":{}}
203+
< {"jsonrpc":"2.0","id":1,"result":[...]}
204+
> {"jsonrpc":"2.0","id":2,"method":"screenshot","params":{"deviceId":"your-device-id"}}
205+
< {"jsonrpc":"2.0","id":2,"result":{...}}
206+
```
207+
208+
**Note**: `screencapture` is not supported over WebSocket - use the HTTP `/rpc` endpoint for video streaming.
209+
181210
## Platform-Specific Notes
182211

183212
### iOS Real Devices

devices/wda/active-app.go

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,51 @@
11
package wda
22

3-
import "fmt"
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
// activeAppValue represents the value field from the WDA active app response
9+
type activeAppValue struct {
10+
BundleID string `json:"bundleId"`
11+
Name string `json:"name"`
12+
PID float64 `json:"pid"`
13+
}
14+
15+
// activeAppResponse represents the WDA API response for active app info
16+
type activeAppResponse struct {
17+
Value activeAppValue `json:"value"`
18+
}
419

520
// ActiveAppInfo represents information about the currently active application
621
type ActiveAppInfo struct {
7-
BundleID string `json:"bundleId"`
8-
Name string `json:"name"`
9-
ProcessID int `json:"pid"`
22+
BundleID string `json:"bundleId"`
23+
Name string `json:"name"`
24+
ProcessID int `json:"pid"`
1025
}
1126

1227
// GetActiveAppInfo returns information about the currently active/foreground application
1328
// This uses the /wda/activeAppInfo endpoint which doesn't require a session
1429
func (c *WdaClient) GetActiveAppInfo() (*ActiveAppInfo, error) {
15-
response, err := c.GetEndpoint("wda/activeAppInfo")
30+
responseMap, err := c.GetEndpoint("wda/activeAppInfo")
1631
if err != nil {
1732
return nil, fmt.Errorf("failed to get active app info: %w", err)
1833
}
1934

20-
// extract value from response
21-
value, ok := response["value"].(map[string]interface{})
22-
if !ok {
23-
return nil, fmt.Errorf("unexpected response format: missing or invalid 'value' field")
35+
// marshal the response back to json and unmarshal into typed struct
36+
jsonData, err := json.Marshal(responseMap)
37+
if err != nil {
38+
return nil, fmt.Errorf("failed to marshal response: %w", err)
2439
}
2540

26-
bundleID, _ := value["bundleId"].(string)
27-
name, _ := value["name"].(string)
28-
pid := 0
29-
if pidFloat, ok := value["pid"].(float64); ok {
30-
pid = int(pidFloat)
41+
var response activeAppResponse
42+
if err := json.Unmarshal(jsonData, &response); err != nil {
43+
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
3144
}
3245

3346
return &ActiveAppInfo{
34-
BundleID: bundleID,
35-
Name: name,
36-
ProcessID: pid,
47+
BundleID: response.Value.BundleID,
48+
Name: response.Value.Name,
49+
ProcessID: int(response.Value.PID),
3750
}, nil
3851
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.25.0
44

55
require (
66
github.com/danielpaulus/go-ios v1.0.182
7+
github.com/gorilla/websocket v1.5.3
78
github.com/sirupsen/logrus v1.9.3
89
github.com/spf13/cobra v1.9.1
910
github.com/stretchr/testify v1.10.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE
2828
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
2929
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
3030
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
31+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
32+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
3133
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
3234
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
3335
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ func StartServer(addr string, enableCORS bool) error {
103103

104104
mux.HandleFunc("/", sendBanner)
105105
mux.HandleFunc("/rpc", handleJSONRPC)
106+
mux.HandleFunc("/ws", NewWebSocketHandler(enableCORS))
106107

107108
// if host is missing, default to localhost
108109
if !strings.Contains(addr, ":") {

0 commit comments

Comments
 (0)