appium-ios-remotexpc 0.0.1

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.
Files changed (92) hide show
  1. package/.github/dependabot.yml +38 -0
  2. package/.github/workflows/format-check.yml +43 -0
  3. package/.github/workflows/lint-and-build.yml +40 -0
  4. package/.github/workflows/pr-title.yml +16 -0
  5. package/.github/workflows/publish.js.yml +42 -0
  6. package/.github/workflows/test-validation.yml +40 -0
  7. package/.mocharc.json +8 -0
  8. package/.prettierignore +3 -0
  9. package/.prettierrc +17 -0
  10. package/.releaserc +37 -0
  11. package/CHANGELOG.md +63 -0
  12. package/LICENSE +201 -0
  13. package/README.md +178 -0
  14. package/assets/images/ios-arch.png +0 -0
  15. package/eslint.config.js +45 -0
  16. package/package.json +78 -0
  17. package/scripts/test-tunnel-creation.ts +378 -0
  18. package/src/base-plist-service.ts +83 -0
  19. package/src/base-socket-service.ts +55 -0
  20. package/src/index.ts +34 -0
  21. package/src/lib/apple-tv/constants.ts +83 -0
  22. package/src/lib/apple-tv/errors.ts +31 -0
  23. package/src/lib/apple-tv/tlv/decoder.ts +68 -0
  24. package/src/lib/apple-tv/tlv/encoder.ts +33 -0
  25. package/src/lib/apple-tv/tlv/index.ts +6 -0
  26. package/src/lib/apple-tv/tlv/pairing-tlv.ts +31 -0
  27. package/src/lib/apple-tv/types.ts +58 -0
  28. package/src/lib/apple-tv/utils/buffer-utils.ts +90 -0
  29. package/src/lib/apple-tv/utils/index.ts +2 -0
  30. package/src/lib/apple-tv/utils/uuid-generator.ts +43 -0
  31. package/src/lib/lockdown/index.ts +468 -0
  32. package/src/lib/pair-record/index.ts +8 -0
  33. package/src/lib/pair-record/pair-record.ts +133 -0
  34. package/src/lib/plist/binary-plist-creator.ts +571 -0
  35. package/src/lib/plist/binary-plist-parser.ts +587 -0
  36. package/src/lib/plist/constants.ts +53 -0
  37. package/src/lib/plist/index.ts +54 -0
  38. package/src/lib/plist/length-based-splitter.ts +326 -0
  39. package/src/lib/plist/plist-creator.ts +42 -0
  40. package/src/lib/plist/plist-decoder.ts +135 -0
  41. package/src/lib/plist/plist-encoder.ts +36 -0
  42. package/src/lib/plist/plist-parser.ts +144 -0
  43. package/src/lib/plist/plist-service.ts +231 -0
  44. package/src/lib/plist/unified-plist-creator.ts +19 -0
  45. package/src/lib/plist/unified-plist-parser.ts +25 -0
  46. package/src/lib/plist/utils.ts +376 -0
  47. package/src/lib/remote-xpc/constants.ts +22 -0
  48. package/src/lib/remote-xpc/handshake-frames.ts +377 -0
  49. package/src/lib/remote-xpc/handshake.ts +152 -0
  50. package/src/lib/remote-xpc/remote-xpc-connection.ts +461 -0
  51. package/src/lib/remote-xpc/xpc-protocol.ts +412 -0
  52. package/src/lib/tunnel/index.ts +253 -0
  53. package/src/lib/tunnel/packet-stream-client.ts +185 -0
  54. package/src/lib/tunnel/packet-stream-server.ts +133 -0
  55. package/src/lib/tunnel/tunnel-api-client.ts +234 -0
  56. package/src/lib/tunnel/tunnel-registry-server.ts +410 -0
  57. package/src/lib/types.ts +291 -0
  58. package/src/lib/usbmux/index.ts +630 -0
  59. package/src/lib/usbmux/usbmux-decoder.ts +66 -0
  60. package/src/lib/usbmux/usbmux-encoder.ts +55 -0
  61. package/src/service-connection.ts +79 -0
  62. package/src/services/index.ts +15 -0
  63. package/src/services/ios/base-service.ts +81 -0
  64. package/src/services/ios/diagnostic-service/index.ts +241 -0
  65. package/src/services/ios/diagnostic-service/keys.ts +770 -0
  66. package/src/services/ios/syslog-service/index.ts +387 -0
  67. package/src/services/ios/tunnel-service/index.ts +88 -0
  68. package/src/services.ts +81 -0
  69. package/test/integration/diagnostics-test.ts +44 -0
  70. package/test/integration/read-pair-record-test.ts +39 -0
  71. package/test/integration/tunnel-test.ts +104 -0
  72. package/test/unit/apple-tv/tlv/decoder.spec.ts +144 -0
  73. package/test/unit/apple-tv/tlv/encoder.spec.ts +91 -0
  74. package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +101 -0
  75. package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +146 -0
  76. package/test/unit/apple-tv/utils/buffer-utils.spec.ts +74 -0
  77. package/test/unit/apple-tv/utils/uuid-generator.spec.ts +39 -0
  78. package/test/unit/fixtures/index.ts +88 -0
  79. package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
  80. package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
  81. package/test/unit/plist/error-handling.spec.ts +101 -0
  82. package/test/unit/plist/fixtures/sample.binary.plist +0 -0
  83. package/test/unit/plist/fixtures/sample.xml.plist +38 -0
  84. package/test/unit/plist/plist-parser.spec.ts +283 -0
  85. package/test/unit/plist/plist.spec.ts +205 -0
  86. package/test/unit/plist/tag-position-handling.spec.ts +90 -0
  87. package/test/unit/plist/unified-plist-parser.spec.ts +227 -0
  88. package/test/unit/plist/utils.spec.ts +249 -0
  89. package/test/unit/plist/xml-cleaning.spec.ts +60 -0
  90. package/test/unit/tunnel/tunnel-registry-server.spec.ts +194 -0
  91. package/test/unit/usbmux/usbmux-specs.ts +71 -0
  92. package/tsconfig.json +36 -0
@@ -0,0 +1,387 @@
1
+ import { logger } from '@appium/support';
2
+ import { EventEmitter } from 'events';
3
+ import type { PacketConsumer, PacketData } from 'tuntap-bridge';
4
+
5
+ import { isBinaryPlist } from '../../../lib/plist/binary-plist-parser.js';
6
+ import { parsePlist } from '../../../lib/plist/unified-plist-parser.js';
7
+ import type {
8
+ PacketSource,
9
+ SyslogOptions,
10
+ SyslogService as SyslogServiceInterface,
11
+ } from '../../../lib/types.js';
12
+ import { ServiceConnection } from '../../../service-connection.js';
13
+ import { BaseService, type Service } from '../base-service.js';
14
+
15
+ const syslogLog = logger.getLogger('SyslogMessages');
16
+ const log = logger.getLogger('Syslog');
17
+
18
+ const MIN_PRINTABLE_RATIO = 0.5;
19
+ const ASCII_PRINTABLE_MIN = 32;
20
+ const ASCII_PRINTABLE_MAX = 126;
21
+ const NON_PRINTABLE_ASCII_REGEX = /[^\x20-\x7E]/g;
22
+ const PLIST_XML_MARKERS = ['<?xml', '<plist'];
23
+ const BINARY_PLIST_MARKER = 'bplist';
24
+ const BINARY_PLIST_MARKER_ALT = 'Ibplist00';
25
+ const MIN_PLIST_SIZE = 8;
26
+ const PLIST_HEADER_CHECK_SIZE = 100;
27
+
28
+ const DEFAULT_SYSLOG_REQUEST = {
29
+ Request: 'StartActivity',
30
+ MessageFilter: 65535,
31
+ StreamFlags: 60,
32
+ } as const;
33
+
34
+ /**
35
+ * syslog-service provides functionality to capture and process syslog messages
36
+ * from a remote device using Apple's XPC services.
37
+ */
38
+ class SyslogService extends EventEmitter implements SyslogServiceInterface {
39
+ private readonly baseService: BaseService;
40
+ private connection: ServiceConnection | null = null;
41
+ private packetConsumer: PacketConsumer | null = null;
42
+ private packetStreamPromise: Promise<void> | null = null;
43
+ private isCapturing = false;
44
+ private enableVerboseLogging = false;
45
+
46
+ /**
47
+ * Creates a new syslog-service instance
48
+ * @param address Tuple containing [host, port]
49
+ */
50
+ constructor(address: [string, number]) {
51
+ super();
52
+ this.baseService = new BaseService(address);
53
+ }
54
+
55
+ /**
56
+ * Starts capturing syslog data from the device
57
+ * @param service Service information
58
+ * @param packetSource Source of packet data (can be PacketConsumer or AsyncIterable)
59
+ * @param options Configuration options for syslog capture
60
+ * @returns Promise resolving to the initial response from the service
61
+ */
62
+ async start(
63
+ service: Service,
64
+ packetSource: PacketSource | AsyncIterable<PacketData>,
65
+ options: SyslogOptions = {},
66
+ ): Promise<void> {
67
+ if (this.isCapturing) {
68
+ log.info(
69
+ 'Syslog capture already in progress. Stopping previous capture.',
70
+ );
71
+ await this.stop();
72
+ }
73
+
74
+ const { pid = -1, enableVerboseLogging = false } = options;
75
+ this.enableVerboseLogging = enableVerboseLogging;
76
+ this.isCapturing = true;
77
+
78
+ this.attachPacketSource(packetSource);
79
+
80
+ try {
81
+ this.connection = await this.baseService.startLockdownService(service);
82
+
83
+ const request = {
84
+ ...DEFAULT_SYSLOG_REQUEST,
85
+ Pid: pid,
86
+ };
87
+
88
+ const response = await this.connection.sendPlistRequest(request);
89
+ log.info(`Syslog capture started: ${response}`);
90
+ this.emit('start', response);
91
+ } catch (error) {
92
+ this.isCapturing = false;
93
+ this.detachPacketSource();
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Stops capturing syslog data
100
+ * @returns Promise that resolves when capture is stopped
101
+ */
102
+ async stop(): Promise<void> {
103
+ if (!this.isCapturing) {
104
+ log.info('No syslog capture in progress.');
105
+ return;
106
+ }
107
+
108
+ this.detachPacketSource();
109
+ this.closeConnection();
110
+
111
+ this.isCapturing = false;
112
+ log.info('Syslog capture stopped');
113
+ this.emit('stop');
114
+ }
115
+
116
+ /**
117
+ * Restart the device
118
+ * @param service Service information
119
+ * @returns Promise that resolves when the restart request is sent
120
+ */
121
+ async restart(service: Service): Promise<void> {
122
+ try {
123
+ const conn = await this.baseService.startLockdownService(service);
124
+ const request = { Request: 'Restart' };
125
+ const res = await conn.sendPlistRequest(request);
126
+ log.info(`Restart response: ${res}`);
127
+ } catch (error) {
128
+ log.error(`Error during restart: ${error}`);
129
+ throw error;
130
+ }
131
+ }
132
+
133
+ private attachPacketSource(
134
+ packetSource: PacketSource | AsyncIterable<PacketData>,
135
+ ): void {
136
+ if (this.isPacketSource(packetSource)) {
137
+ this.packetConsumer = {
138
+ onPacket: (packet: PacketData) => this.processPacket(packet),
139
+ };
140
+ packetSource.addPacketConsumer(this.packetConsumer);
141
+ } else {
142
+ // Store the promise so we can handle it properly
143
+ this.packetStreamPromise = this.processPacketStream(packetSource);
144
+
145
+ // Handle any errors from the stream processing
146
+ this.packetStreamPromise.catch((error) => {
147
+ log.error(`Packet stream processing failed: ${error}`);
148
+ this.emit('error', error);
149
+ });
150
+ }
151
+ }
152
+
153
+ private isPacketSource(source: unknown): source is PacketSource {
154
+ return (
155
+ typeof source === 'object' &&
156
+ source !== null &&
157
+ 'addPacketConsumer' in source &&
158
+ 'removePacketConsumer' in source
159
+ );
160
+ }
161
+
162
+ private async processPacketStream(
163
+ packetStream: AsyncIterable<PacketData>,
164
+ ): Promise<void> {
165
+ try {
166
+ for await (const packet of packetStream) {
167
+ if (!this.isCapturing) {
168
+ break;
169
+ }
170
+ this.processPacket(packet);
171
+ }
172
+ } catch (error) {
173
+ log.error(`Error processing packet stream: ${error}`);
174
+ }
175
+ }
176
+
177
+ private processPacket(packet: PacketData): void {
178
+ if (packet.protocol === 'TCP') {
179
+ this.processTcpPacket(packet);
180
+ } else if (packet.protocol === 'UDP') {
181
+ this.processUdpPacket(packet);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Detaches the packet source
187
+ */
188
+ private detachPacketSource(): void {
189
+ if (this.packetConsumer) {
190
+ this.packetConsumer = null;
191
+ }
192
+
193
+ // Cancel the packet stream processing if it's running
194
+ if (this.packetStreamPromise) {
195
+ // Setting isCapturing to false will cause the stream loop to exit
196
+ this.packetStreamPromise = null;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Closes the current connection
202
+ */
203
+ private closeConnection(): void {
204
+ if (!this.connection) {
205
+ return;
206
+ }
207
+
208
+ try {
209
+ this.connection.close();
210
+ } catch (error) {
211
+ log.debug(`Error closing connection: ${error}`);
212
+ } finally {
213
+ this.connection = null;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Processes a TCP packet
219
+ * @param packet TCP packet to process
220
+ */
221
+ private processTcpPacket(packet: PacketData): void {
222
+ try {
223
+ if (this.mightBePlist(packet.payload)) {
224
+ this.processPlistPacket(packet);
225
+ } else {
226
+ this.processTextPacket(packet);
227
+ }
228
+ } catch (error) {
229
+ log.debug(`Error processing packet: ${error}`);
230
+ this.emitTextMessage(packet.payload);
231
+ }
232
+
233
+ this.logPacketDetails(packet);
234
+ }
235
+
236
+ private processPlistPacket(packet: PacketData): void {
237
+ try {
238
+ const plistData = parsePlist(packet.payload);
239
+ log.debug('Successfully parsed packet as plist');
240
+ this.emit('plist', plistData);
241
+
242
+ const message = JSON.stringify(plistData);
243
+ this.emitMessage(message);
244
+ } catch (error) {
245
+ log.debug(`Failed to parse as plist: ${error}`);
246
+ this.processTextPacket(packet);
247
+ }
248
+ }
249
+
250
+ private processTextPacket(packet: PacketData): void {
251
+ const message = this.extractPrintableText(packet.payload);
252
+ if (!message.trim()) {
253
+ log.debug('TCP packet contains no printable text, ignoring.');
254
+ return;
255
+ }
256
+
257
+ const isMostlyPrintable = this.isMostlyPrintable(packet.payload);
258
+ if (!isMostlyPrintable) {
259
+ log.debug(
260
+ `TCP packet not mostly printable, but contains text: ${message}`,
261
+ );
262
+ }
263
+
264
+ this.emitMessage(message);
265
+ }
266
+
267
+ private emitTextMessage(buffer: Buffer): void {
268
+ const message = this.extractPrintableText(buffer);
269
+ if (message.trim()) {
270
+ this.emitMessage(message);
271
+ }
272
+ }
273
+
274
+ private emitMessage(message: string): void {
275
+ if (this.enableVerboseLogging) {
276
+ syslogLog.info(message);
277
+ }
278
+ this.emit('message', message);
279
+ }
280
+
281
+ /**
282
+ * Checks if the buffer might be a plist (XML or binary)
283
+ * @param buffer Buffer to check
284
+ * @returns True if the buffer might be a plist
285
+ */
286
+ private mightBePlist(buffer: Buffer): boolean {
287
+ try {
288
+ if (buffer.length < MIN_PLIST_SIZE) {
289
+ return false;
290
+ }
291
+
292
+ // Check for XML plist
293
+ const headerStr = buffer.toString(
294
+ 'utf8',
295
+ 0,
296
+ Math.min(PLIST_HEADER_CHECK_SIZE, buffer.length),
297
+ );
298
+ if (PLIST_XML_MARKERS.every((marker) => headerStr.includes(marker))) {
299
+ return true;
300
+ }
301
+
302
+ // Check for binary plist
303
+ if (isBinaryPlist(buffer)) {
304
+ return true;
305
+ }
306
+
307
+ // Check alternative binary plist markers
308
+ const firstNineChars = buffer.toString(
309
+ 'ascii',
310
+ 0,
311
+ Math.min(9, buffer.length),
312
+ );
313
+ return (
314
+ firstNineChars === BINARY_PLIST_MARKER_ALT ||
315
+ firstNineChars.includes(BINARY_PLIST_MARKER)
316
+ );
317
+ } catch (error) {
318
+ log.debug(`Error checking if buffer is plist: ${error}`);
319
+ return false;
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Processes a UDP packet
325
+ * @param packet UDP packet to process
326
+ */
327
+ private processUdpPacket(packet: PacketData): void {
328
+ log.debug(`Received UDP packet (not filtered here): ${packet}`);
329
+ }
330
+
331
+ /**
332
+ * Logs packet details for debugging
333
+ * @param packet Packet to log details for
334
+ */
335
+ private logPacketDetails(packet: PacketData): void {
336
+ log.debug('Received syslog-like TCP packet:');
337
+ log.debug(` Source: ${packet.src}`);
338
+ log.debug(` Destination: ${packet.dst}`);
339
+ log.debug(` Source port: ${packet.sourcePort}`);
340
+ log.debug(` Destination port: ${packet.destPort}`);
341
+ log.debug(` Payload length: ${packet.payload.length}`);
342
+ }
343
+
344
+ /**
345
+ * Extracts printable text from a buffer
346
+ * @param buffer Buffer to extract text from
347
+ * @returns Printable text
348
+ */
349
+ private extractPrintableText(buffer: Buffer): string {
350
+ return buffer.toString().replace(NON_PRINTABLE_ASCII_REGEX, '');
351
+ }
352
+
353
+ /**
354
+ * Determines if a buffer contains mostly printable ASCII characters
355
+ * @param buffer Buffer to analyze
356
+ * @returns True if more than 50% of characters are printable ASCII
357
+ */
358
+ private isMostlyPrintable(buffer: Buffer): boolean {
359
+ try {
360
+ const str = buffer.toString('utf8');
361
+ if (!str || str.length === 0) {
362
+ return false;
363
+ }
364
+
365
+ const totalLength = str.length;
366
+ const threshold = totalLength * MIN_PRINTABLE_RATIO;
367
+ let printableCount = 0;
368
+
369
+ for (let i = 0; i < totalLength; i++) {
370
+ const code = str.charCodeAt(i);
371
+ if (code >= ASCII_PRINTABLE_MIN && code <= ASCII_PRINTABLE_MAX) {
372
+ printableCount++;
373
+ if (printableCount > threshold) {
374
+ return true;
375
+ }
376
+ }
377
+ }
378
+
379
+ return printableCount / totalLength > MIN_PRINTABLE_RATIO;
380
+ } catch (error) {
381
+ log.debug(error);
382
+ return false;
383
+ }
384
+ }
385
+ }
386
+
387
+ export default SyslogService;
@@ -0,0 +1,88 @@
1
+ import { logger } from '@appium/support';
2
+ import { TLSSocket } from 'tls';
3
+
4
+ import {
5
+ LockdownService,
6
+ upgradeSocketToTLS,
7
+ } from '../../../lib/lockdown/index.js';
8
+ import { PlistService } from '../../../lib/plist/plist-service.js';
9
+ import { createUsbmux } from '../../../lib/usbmux/index.js';
10
+
11
+ const log = logger.getLogger('TunnelService');
12
+ const LABEL = 'appium-internal';
13
+
14
+ /**
15
+ * Starts a CoreDeviceProxy session over an existing TLS-upgraded lockdown connection.
16
+ *
17
+ * @param lockdownClient - The TLS-upgraded lockdown client used to send the StartService request.
18
+ * @param deviceID - The device identifier to be used in the Connect request.
19
+ * @param udid - The device UDID used to retrieve the pair record.
20
+ * @param tlsOptions - TLS options for upgrading the usbmuxd socket.
21
+ * @returns A promise that resolves with a TLS-upgraded socket and PlistService for communication with CoreDeviceProxy.
22
+ */
23
+ export async function startCoreDeviceProxy(
24
+ lockdownClient: LockdownService,
25
+ deviceID: number | string,
26
+ udid: string,
27
+ tlsOptions: Partial<import('tls').ConnectionOptions> = {},
28
+ ): Promise<{ socket: TLSSocket; plistService: PlistService }> {
29
+ // Wait for TLS upgrade to complete if in progress
30
+ await lockdownClient.waitForTLSUpgrade();
31
+
32
+ const response = await lockdownClient.sendAndReceive({
33
+ Label: LABEL,
34
+ Request: 'StartService',
35
+ Service: 'com.apple.internal.devicecompute.CoreDeviceProxy',
36
+ EscrowBag: null,
37
+ });
38
+
39
+ lockdownClient.close();
40
+
41
+ if (!response.Port) {
42
+ throw new Error('Service didnt return a port');
43
+ }
44
+
45
+ log.debug(`Connecting to CoreDeviceProxy service on port: ${response.Port}`);
46
+
47
+ const usbmux = await createUsbmux();
48
+ try {
49
+ const pairRecord = await usbmux.readPairRecord(udid);
50
+ if (
51
+ !pairRecord ||
52
+ !pairRecord.HostCertificate ||
53
+ !pairRecord.HostPrivateKey
54
+ ) {
55
+ throw new Error(
56
+ 'Missing required pair record or certificates for TLS upgrade',
57
+ );
58
+ }
59
+
60
+ const coreDeviceSocket = await usbmux.connect(
61
+ Number(deviceID),
62
+ Number(response.Port),
63
+ );
64
+
65
+ log.debug('Socket connected to CoreDeviceProxy, upgrading to TLS...');
66
+
67
+ const fullTlsOptions = {
68
+ ...tlsOptions,
69
+ cert: pairRecord.HostCertificate,
70
+ key: pairRecord.HostPrivateKey,
71
+ };
72
+
73
+ const tlsSocket = await upgradeSocketToTLS(
74
+ coreDeviceSocket,
75
+ fullTlsOptions,
76
+ );
77
+
78
+ const plistService = new PlistService(tlsSocket);
79
+
80
+ return { socket: tlsSocket, plistService };
81
+ } catch (err) {
82
+ // If we haven't connected yet, we can safely close the usbmux
83
+ await usbmux
84
+ .close()
85
+ .catch((closeErr) => log.error(`Error closing usbmux: ${closeErr}`));
86
+ throw err;
87
+ }
88
+ }
@@ -0,0 +1,81 @@
1
+ import { strongbox } from '@appium/strongbox';
2
+
3
+ import RemoteXpcConnection from './lib/remote-xpc/remote-xpc-connection.js';
4
+ import { TunnelManager } from './lib/tunnel/index.js';
5
+ import { TunnelApiClient } from './lib/tunnel/tunnel-api-client.js';
6
+ import type {
7
+ DiagnosticsService as DiagnosticsServiceType,
8
+ SyslogService as SyslogServiceType,
9
+ } from './lib/types.js';
10
+ import DiagnosticsService from './services/ios/diagnostic-service/index.js';
11
+ import SyslogService from './services/ios/syslog-service/index.js';
12
+
13
+ const APPIUM_XCUITEST_DRIVER_NAME = 'appium-xcuitest-driver';
14
+ const TUNNEL_REGISTRY_PORT = 'tunnelRegistryPort';
15
+
16
+ export async function startDiagnosticsService(
17
+ udid: string,
18
+ ): Promise<DiagnosticsServiceType> {
19
+ const { remoteXPC, tunnelConnection } = await createRemoteXPCConnection(udid);
20
+ const diagnosticsService = remoteXPC.findService(
21
+ DiagnosticsService.RSD_SERVICE_NAME,
22
+ );
23
+ return new DiagnosticsService([
24
+ tunnelConnection.host,
25
+ parseInt(diagnosticsService.port, 10),
26
+ ]);
27
+ }
28
+
29
+ export async function startSyslogService(
30
+ udid: string,
31
+ ): Promise<SyslogServiceType> {
32
+ const { tunnelConnection } = await createRemoteXPCConnection(udid);
33
+ return new SyslogService([tunnelConnection.host, tunnelConnection.port]);
34
+ }
35
+
36
+ export async function createRemoteXPCConnection(udid: string) {
37
+ const tunnelConnection = await getTunnelInformation(udid);
38
+ const remoteXPC = await startService(
39
+ tunnelConnection.host,
40
+ tunnelConnection.port,
41
+ );
42
+ return { remoteXPC, tunnelConnection };
43
+ }
44
+
45
+ // #region Private Functions
46
+
47
+ async function getTunnelInformation(udid: string) {
48
+ const box = strongbox(APPIUM_XCUITEST_DRIVER_NAME);
49
+ const item = await box.createItem(TUNNEL_REGISTRY_PORT);
50
+ const tunnelRegistryPort = await item.read();
51
+ if (tunnelRegistryPort === undefined) {
52
+ throw new Error(
53
+ 'Tunnel registry port not found. Please run the tunnel creation script first: sudo appium driver run xcuitest tunnel-creation',
54
+ );
55
+ }
56
+ const tunnelApiClient = new TunnelApiClient(
57
+ `http://127.0.0.1:${tunnelRegistryPort}/remotexpc/tunnels`,
58
+ );
59
+ const tunnelExists = await tunnelApiClient.hasTunnel(udid);
60
+ if (!tunnelExists) {
61
+ throw new Error(
62
+ `No tunnel found for device ${udid}. Please run the tunnel creation script first: sudo appium driver run xcuitest tunnel-creation`,
63
+ );
64
+ }
65
+ const tunnelConnection = await tunnelApiClient.getTunnelConnection(udid);
66
+ if (!tunnelConnection) {
67
+ throw new Error(
68
+ `Failed to get tunnel connection details for device ${udid}`,
69
+ );
70
+ }
71
+ return tunnelConnection;
72
+ }
73
+
74
+ async function startService(
75
+ host: string,
76
+ port: number,
77
+ ): Promise<RemoteXpcConnection> {
78
+ return await TunnelManager.createRemoteXPCConnection(host, port);
79
+ }
80
+
81
+ // #endregion
@@ -0,0 +1,44 @@
1
+ import { expect } from 'chai';
2
+
3
+ import { Services } from '../../src/index.js';
4
+ import type { DiagnosticsService } from '../../src/lib/types.js';
5
+
6
+ describe('Diagnostics Service', function () {
7
+ // Increase timeout for integration tests
8
+ this.timeout(60000);
9
+
10
+ let remoteXPC: any;
11
+ let diagService: DiagnosticsService;
12
+ const udid = process.env.UDID || '';
13
+
14
+ before(async function () {
15
+ diagService = await Services.startDiagnosticsService(udid);
16
+ });
17
+
18
+ after(async function () {
19
+ // Close RemoteXPC connection
20
+ if (remoteXPC) {
21
+ try {
22
+ await remoteXPC.close();
23
+ } catch (error) {
24
+ // Ignore cleanup errors in tests
25
+ }
26
+ }
27
+ });
28
+
29
+ it('should query power information using ioregistry', async function () {
30
+ const rawInfo = await diagService.ioregistry({
31
+ ioClass: 'IOPMPowerSource',
32
+ returnRawJson: true,
33
+ });
34
+ expect(rawInfo).to.be.an('object');
35
+ });
36
+
37
+ it('should query wifi information using ioregistry ', async function () {
38
+ const wifiInfo = await diagService.ioregistry({
39
+ name: 'AppleBCMWLANSkywalkInterface',
40
+ returnRawJson: true,
41
+ });
42
+ expect(wifiInfo).to.be.an('object');
43
+ });
44
+ });
@@ -0,0 +1,39 @@
1
+ import { expect } from 'chai';
2
+
3
+ import { createUsbmux } from '../../src/lib/usbmux/index.js';
4
+
5
+ describe('Pair Record', function () {
6
+ // Increase timeout for integration tests
7
+ this.timeout(60000);
8
+
9
+ let usb: any;
10
+
11
+ before(async function () {
12
+ usb = await createUsbmux();
13
+ });
14
+
15
+ after(async function () {
16
+ if (usb) {
17
+ await usb.close();
18
+ }
19
+ });
20
+
21
+ it('should read pair record', async function () {
22
+ try {
23
+ await usb.readPairRecord('');
24
+ // If no error is thrown, the test passes
25
+ expect(true).to.be.true;
26
+ } catch (err) {
27
+ console.log(err);
28
+ // If the error is expected (e.g., no pair record found), the test can still pass
29
+ // Otherwise, fail the test
30
+ expect(err).to.not.be.undefined;
31
+ }
32
+ });
33
+
34
+ it('should list devices', async function () {
35
+ const devices = await usb.listDevices();
36
+ console.log(devices);
37
+ expect(devices).to.be.an('array');
38
+ });
39
+ });