appium-ios-remotexpc 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/services/ios/webinspector/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE3E,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIjD;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,eAAe,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,qBAAa,mBAAoB,SAAQ,WAAW;IAClD,MAAM,CAAC,QAAQ,CAAC,gBAAgB,wCAAwC;IAGxE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAA4B;IACzE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,8BAA8B,CACnB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,8BAA8B,CACnB;IACnC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAA6B;IAC5E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sCAAsC,CACpB;IAC1C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAA8B;IAC9E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAA6B;IAC5E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CACpB;IAEjC,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,cAAc,CAA8B;gBAExC,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;IAKrC;;;;;OAKG;IACG,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC;IAiBhB;;;OAGG;IACI,aAAa,IAAI,cAAc,CAAC,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC;IA6DnE;;OAEG;IACH,aAAa,IAAI,IAAI;IAKrB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B;;;OAGG;IACH,eAAe,IAAI,MAAM;IAIzB;;;OAGG;IACG,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM/D;;OAEG;IACG,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/C;;;OAGG;IACG,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD;;;;;OAKG;IACG,+BAA+B,CACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,CAAC,EAAE,eAAe,GAC7B,OAAO,CAAC,IAAI,CAAC;IAmBhB;;;;;;OAMG;IACG,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,kBAAkB,GAAE,OAAc,GACjC,OAAO,CAAC,IAAI,CAAC;IAkBhB;;;;;;OAMG;IACG,iBAAiB,CACrB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,GAAG,GACR,OAAO,CAAC,IAAI,CAAC;IAYhB;;;;;OAKG;IACG,sBAAsB,CAC1B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,IAAI,CAAC;IAQhB;;;OAGG;YACW,4BAA4B;IA2B1C;;OAEG;IACH,OAAO,CAAC,oBAAoB;CA0B7B;AAED,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,286 @@
1
+ import { logger } from '@appium/support';
2
+ import { randomUUID } from 'crypto';
3
+ import { EventEmitter } from 'events';
4
+ import { ServiceConnection } from '../../../service-connection.js';
5
+ import { BaseService } from '../base-service.js';
6
+ const log = logger.getLogger('WebInspectorService');
7
+ /**
8
+ * WebInspectorService provides an API to:
9
+ * - Send messages to webinspectord
10
+ * - Listen to messages from webinspectord
11
+ * - Communicate with web views and Safari on iOS devices
12
+ *
13
+ * This service is used for web automation, inspection, and debugging.
14
+ */
15
+ export class WebInspectorService extends BaseService {
16
+ static RSD_SERVICE_NAME = 'com.apple.webinspector.shim.remote';
17
+ // RPC method selectors
18
+ static RPC_REPORT_IDENTIFIER = '_rpc_reportIdentifier:';
19
+ static RPC_REQUEST_APPLICATION_LAUNCH = '_rpc_requestApplicationLaunch:';
20
+ static RPC_GET_CONNECTED_APPLICATIONS = '_rpc_getConnectedApplications:';
21
+ static RPC_FORWARD_GET_LISTING = '_rpc_forwardGetListing:';
22
+ static RPC_FORWARD_AUTOMATION_SESSION_REQUEST = '_rpc_forwardAutomationSessionRequest:';
23
+ static RPC_FORWARD_SOCKET_SETUP = '_rpc_forwardSocketSetup:';
24
+ static RPC_FORWARD_SOCKET_DATA = '_rpc_forwardSocketData:';
25
+ static RPC_FORWARD_INDICATE_WEB_VIEW = '_rpc_forwardIndicateWebView:';
26
+ connection = null;
27
+ messageEmitter = new EventEmitter();
28
+ isReceiving = false;
29
+ connectionId;
30
+ receivePromise = null;
31
+ constructor(address) {
32
+ super(address);
33
+ this.connectionId = randomUUID().toUpperCase();
34
+ }
35
+ /**
36
+ * Send a message to the WebInspector service
37
+ * @param selector The RPC selector (e.g., '_rpc_reportIdentifier:')
38
+ * @param args The arguments dictionary for the message
39
+ * @returns Promise that resolves when the message is sent
40
+ */
41
+ async sendMessage(selector, args = {}) {
42
+ const connection = await this.connectToWebInspectorService();
43
+ // Add connection identifier to all messages
44
+ const message = {
45
+ __selector: selector,
46
+ __argument: {
47
+ ...args,
48
+ WIRConnectionIdentifierKey: this.connectionId,
49
+ },
50
+ };
51
+ log.debug(`Sending WebInspector message: ${selector}`);
52
+ connection.sendPlist(message);
53
+ }
54
+ /**
55
+ * Listen to messages from the WebInspector service using async generator
56
+ * @yields PlistMessage - Messages received from the WebInspector service
57
+ */
58
+ async *listenMessage() {
59
+ await this.connectToWebInspectorService();
60
+ // Start receiving messages in background if not already started
61
+ if (!this.isReceiving) {
62
+ this.startMessageReceiver();
63
+ }
64
+ const queue = [];
65
+ let resolveNext = null;
66
+ let stopped = false;
67
+ const messageHandler = (message) => {
68
+ if (resolveNext) {
69
+ resolveNext({ value: message, done: false });
70
+ resolveNext = null;
71
+ }
72
+ else {
73
+ queue.push(message);
74
+ }
75
+ };
76
+ const stopHandler = () => {
77
+ stopped = true;
78
+ if (resolveNext) {
79
+ resolveNext({ value: undefined, done: true });
80
+ resolveNext = null;
81
+ }
82
+ };
83
+ this.messageEmitter.on('message', messageHandler);
84
+ this.messageEmitter.once('stop', stopHandler);
85
+ try {
86
+ while (!stopped) {
87
+ if (queue.length > 0) {
88
+ yield queue.shift();
89
+ }
90
+ else {
91
+ const message = await new Promise((resolve) => {
92
+ if (stopped) {
93
+ resolve(null);
94
+ return;
95
+ }
96
+ resolveNext = (result) => {
97
+ resolve(result.done ? null : result.value);
98
+ };
99
+ });
100
+ if (message === null) {
101
+ break;
102
+ }
103
+ yield message;
104
+ }
105
+ }
106
+ }
107
+ finally {
108
+ this.messageEmitter.off('message', messageHandler);
109
+ this.messageEmitter.off('stop', stopHandler);
110
+ }
111
+ }
112
+ /**
113
+ * Stop listening to messages
114
+ */
115
+ stopListening() {
116
+ this.isReceiving = false;
117
+ this.messageEmitter.emit('stop');
118
+ }
119
+ /**
120
+ * Close the connection and clean up resources
121
+ */
122
+ async close() {
123
+ this.stopListening();
124
+ if (this.connection) {
125
+ await this.connection.close();
126
+ this.connection = null;
127
+ log.debug('WebInspector connection closed');
128
+ }
129
+ if (this.receivePromise) {
130
+ await this.receivePromise;
131
+ }
132
+ }
133
+ /**
134
+ * Get the connection ID being used for this service
135
+ * @returns The connection identifier
136
+ */
137
+ getConnectionId() {
138
+ return this.connectionId;
139
+ }
140
+ /**
141
+ * Request application launch
142
+ * @param bundleId The bundle identifier of the application to launch
143
+ */
144
+ async requestApplicationLaunch(bundleId) {
145
+ await this.sendMessage(WebInspectorService.RPC_REQUEST_APPLICATION_LAUNCH, {
146
+ WIRApplicationBundleIdentifierKey: bundleId,
147
+ });
148
+ }
149
+ /**
150
+ * Get connected applications
151
+ */
152
+ async getConnectedApplications() {
153
+ await this.sendMessage(WebInspectorService.RPC_GET_CONNECTED_APPLICATIONS, {});
154
+ }
155
+ /**
156
+ * Forward get listing for an application
157
+ * @param appId The application identifier
158
+ */
159
+ async forwardGetListing(appId) {
160
+ await this.sendMessage(WebInspectorService.RPC_FORWARD_GET_LISTING, {
161
+ WIRApplicationIdentifierKey: appId,
162
+ });
163
+ }
164
+ /**
165
+ * Forward automation session request
166
+ * @param sessionId The session identifier
167
+ * @param appId The application identifier
168
+ * @param capabilities Optional session capabilities
169
+ */
170
+ async forwardAutomationSessionRequest(sessionId, appId, capabilities) {
171
+ const defaultCapabilities = {
172
+ 'org.webkit.webdriver.webrtc.allow-insecure-media-capture': true,
173
+ 'org.webkit.webdriver.webrtc.suppress-ice-candidate-filtering': false,
174
+ };
175
+ await this.sendMessage(WebInspectorService.RPC_FORWARD_AUTOMATION_SESSION_REQUEST, {
176
+ WIRApplicationIdentifierKey: appId,
177
+ WIRSessionIdentifierKey: sessionId,
178
+ WIRSessionCapabilitiesKey: {
179
+ ...defaultCapabilities,
180
+ ...(capabilities ?? {}),
181
+ },
182
+ });
183
+ }
184
+ /**
185
+ * Forward socket setup for inspector connection
186
+ * @param sessionId The session identifier
187
+ * @param appId The application identifier
188
+ * @param pageId The page identifier
189
+ * @param automaticallyPause Whether to automatically pause (defaults to true)
190
+ */
191
+ async forwardSocketSetup(sessionId, appId, pageId, automaticallyPause = true) {
192
+ const message = {
193
+ WIRApplicationIdentifierKey: appId,
194
+ WIRPageIdentifierKey: pageId,
195
+ WIRSenderKey: sessionId,
196
+ WIRMessageDataTypeChunkSupportedKey: 0,
197
+ };
198
+ if (!automaticallyPause) {
199
+ message.WIRAutomaticallyPause = false;
200
+ }
201
+ await this.sendMessage(WebInspectorService.RPC_FORWARD_SOCKET_SETUP, message);
202
+ }
203
+ /**
204
+ * Forward socket data to a page
205
+ * @param sessionId The session identifier
206
+ * @param appId The application identifier
207
+ * @param pageId The page identifier
208
+ * @param data The data to send (will be JSON stringified)
209
+ */
210
+ async forwardSocketData(sessionId, appId, pageId, data) {
211
+ const socketData = typeof data === 'string' ? data : JSON.stringify(data);
212
+ await this.sendMessage(WebInspectorService.RPC_FORWARD_SOCKET_DATA, {
213
+ WIRApplicationIdentifierKey: appId,
214
+ WIRPageIdentifierKey: pageId,
215
+ WIRSessionIdentifierKey: sessionId,
216
+ WIRSenderKey: sessionId,
217
+ WIRSocketDataKey: Buffer.from(socketData, 'utf-8'),
218
+ });
219
+ }
220
+ /**
221
+ * Forward indicate web view
222
+ * @param appId The application identifier
223
+ * @param pageId The page identifier
224
+ * @param enable Whether to enable indication
225
+ */
226
+ async forwardIndicateWebView(appId, pageId, enable) {
227
+ await this.sendMessage(WebInspectorService.RPC_FORWARD_INDICATE_WEB_VIEW, {
228
+ WIRApplicationIdentifierKey: appId,
229
+ WIRPageIdentifierKey: pageId,
230
+ WIRIndicateEnabledKey: enable,
231
+ });
232
+ }
233
+ /**
234
+ * Connect to the WebInspector service
235
+ * @returns Promise resolving to the ServiceConnection instance
236
+ */
237
+ async connectToWebInspectorService() {
238
+ if (this.connection) {
239
+ return this.connection;
240
+ }
241
+ const service = {
242
+ serviceName: WebInspectorService.RSD_SERVICE_NAME,
243
+ port: this.address[1].toString(),
244
+ };
245
+ this.connection = await this.startLockdownService(service);
246
+ // Consume the StartService response from RSDCheckin
247
+ const startServiceResponse = await this.connection.receive();
248
+ if (startServiceResponse?.Request !== 'StartService') {
249
+ log.warn(`Expected StartService response, got: ${JSON.stringify(startServiceResponse)}`);
250
+ }
251
+ // Send initial identifier report
252
+ await this.sendMessage(WebInspectorService.RPC_REPORT_IDENTIFIER, {});
253
+ log.debug('Connected to WebInspector service');
254
+ return this.connection;
255
+ }
256
+ /**
257
+ * Start receiving messages from the WebInspector service in the background
258
+ */
259
+ startMessageReceiver() {
260
+ if (this.isReceiving || !this.connection) {
261
+ return;
262
+ }
263
+ this.isReceiving = true;
264
+ this.receivePromise = (async () => {
265
+ try {
266
+ while (this.isReceiving && this.connection) {
267
+ try {
268
+ const message = await this.connection.receive();
269
+ this.messageEmitter.emit('message', message);
270
+ }
271
+ catch (error) {
272
+ if (this.isReceiving) {
273
+ log.error('Error receiving message:', error);
274
+ this.messageEmitter.emit('error', error);
275
+ }
276
+ break;
277
+ }
278
+ }
279
+ }
280
+ finally {
281
+ this.isReceiving = false;
282
+ }
283
+ })();
284
+ }
285
+ }
286
+ export default WebInspectorService;
@@ -1,10 +1,12 @@
1
1
  import { RemoteXpcConnection } from './lib/remote-xpc/remote-xpc-connection.js';
2
- import type { DiagnosticsServiceWithConnection, MobileConfigServiceWithConnection, MobileImageMounterServiceWithConnection, NotificationProxyServiceWithConnection, SyslogService as SyslogServiceType } from './lib/types.js';
2
+ import type { DiagnosticsServiceWithConnection, MobileConfigServiceWithConnection, MobileImageMounterServiceWithConnection, NotificationProxyServiceWithConnection, SpringboardServiceWithConnection, SyslogService as SyslogServiceType, WebInspectorServiceWithConnection } from './lib/types.js';
3
3
  export declare function startDiagnosticsService(udid: string): Promise<DiagnosticsServiceWithConnection>;
4
4
  export declare function startNotificationProxyService(udid: string): Promise<NotificationProxyServiceWithConnection>;
5
5
  export declare function startMobileConfigService(udid: string): Promise<MobileConfigServiceWithConnection>;
6
6
  export declare function startMobileImageMounterService(udid: string): Promise<MobileImageMounterServiceWithConnection>;
7
+ export declare function startSpringboardService(udid: string): Promise<SpringboardServiceWithConnection>;
7
8
  export declare function startSyslogService(udid: string): Promise<SyslogServiceType>;
9
+ export declare function startWebInspectorService(udid: string): Promise<WebInspectorServiceWithConnection>;
8
10
  export declare function createRemoteXPCConnection(udid: string): Promise<{
9
11
  remoteXPC: RemoteXpcConnection;
10
12
  tunnelConnection: {
@@ -1 +1 @@
1
- {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/services.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAGhF,OAAO,KAAK,EACV,gCAAgC,EAChC,iCAAiC,EACjC,uCAAuC,EACvC,sCAAsC,EACtC,aAAa,IAAI,iBAAiB,EACnC,MAAM,gBAAgB,CAAC;AAUxB,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gCAAgC,CAAC,CAY3C;AAED,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,sCAAsC,CAAC,CAYjD;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iCAAiC,CAAC,CAY5C;AACD,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,uCAAuC,CAAC,CAYlD;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,MAAM;;;;;;;;GAO3D"}
1
+ {"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/services.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AAGhF,OAAO,KAAK,EACV,gCAAgC,EAChC,iCAAiC,EACjC,uCAAuC,EACvC,sCAAsC,EACtC,gCAAgC,EAChC,aAAa,IAAI,iBAAiB,EAClC,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AAYxB,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gCAAgC,CAAC,CAY3C;AAED,wBAAsB,6BAA6B,CACjD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,sCAAsC,CAAC,CAYjD;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iCAAiC,CAAC,CAY5C;AACD,wBAAsB,8BAA8B,CAClD,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,uCAAuC,CAAC,CAYlD;AAED,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gCAAgC,CAAC,CAY3C;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iCAAiC,CAAC,CAY5C;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,MAAM;;;;;;;;GAO3D"}
@@ -6,7 +6,9 @@ import DiagnosticsService from './services/ios/diagnostic-service/index.js';
6
6
  import { MobileConfigService } from './services/ios/mobile-config/index.js';
7
7
  import MobileImageMounterService from './services/ios/mobile-image-mounter/index.js';
8
8
  import { NotificationProxyService } from './services/ios/notification-proxy/index.js';
9
+ import { SpringBoardService } from './services/ios/springboard-service/index.js';
9
10
  import SyslogService from './services/ios/syslog-service/index.js';
11
+ import { WebInspectorService } from './services/ios/webinspector/index.js';
10
12
  const APPIUM_XCUITEST_DRIVER_NAME = 'appium-xcuitest-driver';
11
13
  const TUNNEL_REGISTRY_PORT = 'tunnelRegistryPort';
12
14
  export async function startDiagnosticsService(udid) {
@@ -53,10 +55,32 @@ export async function startMobileImageMounterService(udid) {
53
55
  ]),
54
56
  };
55
57
  }
58
+ export async function startSpringboardService(udid) {
59
+ const { remoteXPC, tunnelConnection } = await createRemoteXPCConnection(udid);
60
+ const springboardService = remoteXPC.findService(SpringBoardService.RSD_SERVICE_NAME);
61
+ return {
62
+ remoteXPC: remoteXPC,
63
+ springboardService: new SpringBoardService([
64
+ tunnelConnection.host,
65
+ parseInt(springboardService.port, 10),
66
+ ]),
67
+ };
68
+ }
56
69
  export async function startSyslogService(udid) {
57
70
  const { tunnelConnection } = await createRemoteXPCConnection(udid);
58
71
  return new SyslogService([tunnelConnection.host, tunnelConnection.port]);
59
72
  }
73
+ export async function startWebInspectorService(udid) {
74
+ const { remoteXPC, tunnelConnection } = await createRemoteXPCConnection(udid);
75
+ const webInspectorService = remoteXPC.findService(WebInspectorService.RSD_SERVICE_NAME);
76
+ return {
77
+ remoteXPC: remoteXPC,
78
+ webInspectorService: new WebInspectorService([
79
+ tunnelConnection.host,
80
+ parseInt(webInspectorService.port, 10),
81
+ ]),
82
+ };
83
+ }
60
84
  export async function createRemoteXPCConnection(udid) {
61
85
  const tunnelConnection = await getTunnelInformation(udid);
62
86
  const remoteXPC = await startService(tunnelConnection.host, tunnelConnection.port);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-ios-remotexpc",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "main": "build/src/index.js",
5
5
  "types": "build/src/index.d.ts",
6
6
  "type": "module",
@@ -31,6 +31,8 @@
31
31
  "test:notification": "mocha test/integration/notification-proxy-test.ts --exit --timeout 1m",
32
32
  "test:image-mounter": "mocha test/integration/mobile-image-mounter-test.ts --exit --timeout 1m",
33
33
  "test:mobile-config": "mocha test/integration/mobile-config-test.ts --exit --timeout 1m",
34
+ "test:springboard": "mocha test/integration/springboard-service-test.ts --exit --timeout 1m",
35
+ "test:webinspector": "mocha test/integration/webinspector-test.ts --exit --timeout 1m",
34
36
  "test:unit": "mocha 'test/unit/**/*.ts' --exit --timeout 2m",
35
37
  "test:tunnel-creation": "sudo tsx scripts/test-tunnel-creation.ts",
36
38
  "test:tunnel-creation:lsof": "sudo tsx scripts/test-tunnel-creation.ts --keep-open"
package/src/index.ts CHANGED
@@ -17,6 +17,8 @@ export type {
17
17
  MobileImageMounterService,
18
18
  NotificationProxyService,
19
19
  MobileConfigService,
20
+ SpringboardService,
21
+ WebInspectorService,
20
22
  SyslogService,
21
23
  SocketInfo,
22
24
  TunnelResult,
@@ -26,6 +28,8 @@ export type {
26
28
  MobileImageMounterServiceWithConnection,
27
29
  NotificationProxyServiceWithConnection,
28
30
  MobileConfigServiceWithConnection,
31
+ SpringboardServiceWithConnection,
32
+ WebInspectorServiceWithConnection,
29
33
  } from './lib/types.js';
30
34
  export {
31
35
  createUsbmux,
package/src/lib/types.ts CHANGED
@@ -6,6 +6,7 @@ import { EventEmitter } from 'events';
6
6
 
7
7
  import type { ServiceConnection } from '../service-connection.js';
8
8
  import type { BaseService, Service } from '../services/ios/base-service.js';
9
+ import type { InterfaceOrientation } from '../services/ios/springboard-service/index.js';
9
10
  import type { RemoteXpcConnection } from './remote-xpc/remote-xpc-connection.js';
10
11
  import type { Device } from './usbmux/index.js';
11
12
 
@@ -311,6 +312,121 @@ export interface MobileConfigServiceWithConnection {
311
312
  remoteXPC: RemoteXpcConnection;
312
313
  }
313
314
 
315
+ /**
316
+ * Represents the WebInspectorService
317
+ */
318
+ export interface WebInspectorService extends BaseService {
319
+ /**
320
+ * Send a message to the WebInspector service
321
+ * @param selector The RPC selector (e.g., '_rpc_reportIdentifier:')
322
+ * @param args The arguments dictionary for the message
323
+ * @returns Promise that resolves when the message is sent
324
+ */
325
+ sendMessage(selector: string, args?: PlistDictionary): Promise<void>;
326
+
327
+ /**
328
+ * Listen to messages from the WebInspector service using async generator
329
+ * @yields PlistMessage - Messages received from the WebInspector service
330
+ */
331
+ listenMessage(): AsyncGenerator<PlistMessage, void, unknown>;
332
+
333
+ /**
334
+ * Stop listening to messages
335
+ */
336
+ stopListening(): void;
337
+
338
+ /**
339
+ * Close the connection and clean up resources
340
+ */
341
+ close(): Promise<void>;
342
+
343
+ /**
344
+ * Get the connection ID being used for this service
345
+ * @returns The connection identifier
346
+ */
347
+ getConnectionId(): string;
348
+
349
+ /**
350
+ * Request application launch
351
+ * @param bundleId The bundle identifier of the application to launch
352
+ */
353
+ requestApplicationLaunch(bundleId: string): Promise<void>;
354
+
355
+ /**
356
+ * Get connected applications
357
+ */
358
+ getConnectedApplications(): Promise<void>;
359
+
360
+ /**
361
+ * Forward get listing for an application
362
+ * @param appId The application identifier
363
+ */
364
+ forwardGetListing(appId: string): Promise<void>;
365
+
366
+ /**
367
+ * Forward automation session request
368
+ * @param sessionId The session identifier
369
+ * @param appId The application identifier
370
+ * @param capabilities Optional session capabilities
371
+ */
372
+ forwardAutomationSessionRequest(
373
+ sessionId: string,
374
+ appId: string,
375
+ capabilities?: PlistDictionary,
376
+ ): Promise<void>;
377
+
378
+ /**
379
+ * Forward socket setup for inspector connection
380
+ * @param sessionId The session identifier
381
+ * @param appId The application identifier
382
+ * @param pageId The page identifier
383
+ * @param automaticallyPause Whether to automatically pause (defaults to true)
384
+ */
385
+ forwardSocketSetup(
386
+ sessionId: string,
387
+ appId: string,
388
+ pageId: number,
389
+ automaticallyPause?: boolean,
390
+ ): Promise<void>;
391
+
392
+ /**
393
+ * Forward socket data to a page
394
+ * @param sessionId The session identifier
395
+ * @param appId The application identifier
396
+ * @param pageId The page identifier
397
+ * @param data The data to send (will be JSON stringified)
398
+ */
399
+ forwardSocketData(
400
+ sessionId: string,
401
+ appId: string,
402
+ pageId: number,
403
+ data: any,
404
+ ): Promise<void>;
405
+
406
+ /**
407
+ * Forward indicate web view
408
+ * @param appId The application identifier
409
+ * @param pageId The page identifier
410
+ * @param enable Whether to enable indication
411
+ */
412
+ forwardIndicateWebView(
413
+ appId: string,
414
+ pageId: number,
415
+ enable: boolean,
416
+ ): Promise<void>;
417
+ }
418
+
419
+ /**
420
+ * Represents a WebInspectorService instance with its associated RemoteXPC connection
421
+ * This allows callers to properly manage the connection lifecycle
422
+ */
423
+ export interface WebInspectorServiceWithConnection {
424
+ /** The WebInspectorService instance */
425
+ webInspectorService: WebInspectorService;
426
+ /** The RemoteXPC connection that can be used to close the connection */
427
+ remoteXPC: RemoteXpcConnection;
428
+ }
429
+
314
430
  /**
315
431
  * Options for configuring syslog capture
316
432
  */
@@ -508,3 +624,113 @@ export interface MobileImageMounterServiceWithConnection {
508
624
  /** The RemoteXPC connection for service management */
509
625
  remoteXPC: RemoteXpcConnection;
510
626
  }
627
+
628
+ /**
629
+ * Represents the instance side of SpringboardService
630
+ */
631
+ export interface SpringboardService extends BaseService {
632
+ /**
633
+ * Gets the icon state
634
+ * @returns Promise resolving to the icon state
635
+ * e.g.
636
+ * [
637
+ * {
638
+ * displayIdentifier: 'com.apple.MobileSMS',
639
+ * displayName: 'Messages',
640
+ * iconModDate: 2025-09-03T12:55:46.400Z,
641
+ * bundleVersion: '1402.700.63.2.1',
642
+ * bundleIdentifier: 'com.apple.MobileSMS'
643
+ * },
644
+ * {
645
+ * displayIdentifier: 'com.apple.measure',
646
+ * displayName: 'Measure',
647
+ * iconModDate: 2025-09-03T12:55:49.522Z,
648
+ * bundleVersion: '175.100.3.0.1',
649
+ * bundleIdentifier: 'com.apple.measure'
650
+ * },
651
+ * ...
652
+ * ]
653
+ */
654
+ getIconState(): Promise<PlistDictionary>;
655
+
656
+ /**
657
+ * TODO: This does not work currently due to a bug in Apple protocol implementation (maybe?)
658
+ * Sets the icon state
659
+ * @param newState where is the payload from getIconState
660
+ */
661
+ setIconState(newState: PlistDictionary[]): Promise<void>;
662
+
663
+ /**
664
+ * Gets the icon PNG data for a given bundle ID
665
+ * @param bundleID The bundle ID of the app
666
+ * @returns {Promise<Buffer>} which is the PNG data of the app icon
667
+ */
668
+ getIconPNGData(bundleID: string): Promise<Buffer>;
669
+
670
+ /**
671
+ * TODO: This does not work currently due to a bug in Apple protocol implementation
672
+ * Add payload structure when it is fixed
673
+ * Gets wallpaper info
674
+ * @param wallpaperName The name of the wallpaper
675
+ * @returns Promise resolving to the wallpaper info
676
+ */
677
+ getWallpaperInfo(wallpaperName: string): Promise<PlistDictionary>;
678
+
679
+ /**
680
+ * Gets homescreen icon metrics
681
+ * @returns {Promise<PlistDictionary>}
682
+ * e.g.
683
+ * {
684
+ * homeScreenIconHeight: 64,
685
+ * homeScreenIconMaxPages: 15,
686
+ * homeScreenWidth: 414,
687
+ * homeScreenHeight: 896,
688
+ * homeScreenIconDockMaxCount: 4,
689
+ * homeScreenIconFolderMaxPages: 15,
690
+ * homeScreenIconWidth: 64,
691
+ * homeScreenIconRows: 6,
692
+ * homeScreenIconColumns: 4,
693
+ * homeScreenIconFolderColumns: 3,
694
+ * homeScreenIconFolderRows: 3
695
+ * }
696
+ */
697
+ getHomescreenIconMetrics(): Promise<PlistDictionary>;
698
+
699
+ /**
700
+ * Gets the current interface orientation
701
+ * @returns {Promise<InterfaceOrientation>}
702
+ * 1 = Portrait
703
+ * 2 = PortraitUpsideDown
704
+ * 3 = Landscape
705
+ * 4 = LandscapeHomeToLeft
706
+ */
707
+ getInterfaceOrientation(): Promise<InterfaceOrientation>;
708
+
709
+ /**
710
+ * Gets wallpaper preview image for homescreen and lockscreen
711
+ * @param wallpaperName
712
+ * @returns {Promise<Buffer>} which is a wallpaper preview image
713
+ */
714
+ getWallpaperPreviewImage(
715
+ wallpaperName: 'homescreen' | 'lockscreen',
716
+ ): Promise<Buffer>;
717
+
718
+ /**
719
+ * TODO: This does not work currently due to a bug in Apple protocol implementation
720
+ * Use getWallpaperPreviewImage('homescreen') instead
721
+ * Gets wallpaper PNG data
722
+ * @param wallpaperName
723
+ * @returns {Promise<Buffer>}
724
+ */
725
+ getWallpaperPNGData(wallpaperName: string): Promise<Buffer>;
726
+ }
727
+
728
+ /**
729
+ * Represents a SpringboardService instance with its associated RemoteXPC connection
730
+ */
731
+ export interface SpringboardServiceWithConnection {
732
+ /** The SpringboardService instance */
733
+ springboardService: SpringboardService;
734
+ /** The RemoteXPC connection for service management */
735
+ remoteXPC: RemoteXpcConnection;
736
+ }
@@ -60,6 +60,15 @@ export class ServiceConnection extends BasePlistService {
60
60
  return this.sendAndReceive(requestObj, timeout);
61
61
  }
62
62
 
63
+ /**
64
+ * Sends a plist message without waiting for a response
65
+ * This is useful for fire-and-forget style communication
66
+ * @param message The message to send
67
+ */
68
+ sendPlist(message: PlistDictionary): void {
69
+ this.send(message);
70
+ }
71
+
63
72
  /**
64
73
  * Gets the underlying socket
65
74
  * @returns The socket used by this service
@@ -6,12 +6,14 @@ import * as diagnostics from './ios/diagnostic-service/index.js';
6
6
  import * as mobileImageMounter from './ios/mobile-image-mounter/index.js';
7
7
  import * as syslog from './ios/syslog-service/index.js';
8
8
  import * as tunnel from './ios/tunnel-service/index.js';
9
+ import * as webinspector from './ios/webinspector/index.js';
9
10
 
10
11
  export {
11
12
  diagnostics,
12
13
  mobileImageMounter,
13
14
  syslog,
14
15
  tunnel,
16
+ webinspector,
15
17
  TunnelRegistryServer,
16
18
  startTunnelRegistryServer,
17
19
  };