appium-ios-remotexpc 0.13.2 → 0.15.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/src/index.d.ts +1 -1
  3. package/build/src/index.d.ts.map +1 -1
  4. package/build/src/lib/plist/binary-plist-creator.d.ts.map +1 -1
  5. package/build/src/lib/plist/binary-plist-creator.js +30 -0
  6. package/build/src/lib/plist/index.d.ts +1 -0
  7. package/build/src/lib/plist/index.d.ts.map +1 -1
  8. package/build/src/lib/plist/index.js +1 -0
  9. package/build/src/lib/plist/plist-uid.d.ts +10 -0
  10. package/build/src/lib/plist/plist-uid.d.ts.map +1 -0
  11. package/build/src/lib/plist/plist-uid.js +10 -0
  12. package/build/src/lib/types.d.ts +177 -2
  13. package/build/src/lib/types.d.ts.map +1 -1
  14. package/build/src/services/ios/dvt/channel-fragmenter.d.ts +21 -0
  15. package/build/src/services/ios/dvt/channel-fragmenter.d.ts.map +1 -0
  16. package/build/src/services/ios/dvt/channel-fragmenter.js +37 -0
  17. package/build/src/services/ios/dvt/channel.d.ts +32 -0
  18. package/build/src/services/ios/dvt/channel.d.ts.map +1 -0
  19. package/build/src/services/ios/dvt/channel.js +44 -0
  20. package/build/src/services/ios/dvt/dtx-message.d.ts +88 -0
  21. package/build/src/services/ios/dvt/dtx-message.d.ts.map +1 -0
  22. package/build/src/services/ios/dvt/dtx-message.js +113 -0
  23. package/build/src/services/ios/dvt/index.d.ts +119 -0
  24. package/build/src/services/ios/dvt/index.d.ts.map +1 -0
  25. package/build/src/services/ios/dvt/index.js +552 -0
  26. package/build/src/services/ios/dvt/instruments/condition-inducer.d.ts +37 -0
  27. package/build/src/services/ios/dvt/instruments/condition-inducer.d.ts.map +1 -0
  28. package/build/src/services/ios/dvt/instruments/condition-inducer.js +99 -0
  29. package/build/src/services/ios/dvt/instruments/location-simulation.d.ts +43 -0
  30. package/build/src/services/ios/dvt/instruments/location-simulation.d.ts.map +1 -0
  31. package/build/src/services/ios/dvt/instruments/location-simulation.js +60 -0
  32. package/build/src/services/ios/dvt/instruments/screenshot.d.ts +17 -0
  33. package/build/src/services/ios/dvt/instruments/screenshot.d.ts.map +1 -0
  34. package/build/src/services/ios/dvt/instruments/screenshot.js +35 -0
  35. package/build/src/services/ios/dvt/nskeyedarchiver-decoder.d.ts +41 -0
  36. package/build/src/services/ios/dvt/nskeyedarchiver-decoder.d.ts.map +1 -0
  37. package/build/src/services/ios/dvt/nskeyedarchiver-decoder.js +195 -0
  38. package/build/src/services/ios/dvt/utils.d.ts +19 -0
  39. package/build/src/services/ios/dvt/utils.d.ts.map +1 -0
  40. package/build/src/services/ios/dvt/utils.js +67 -0
  41. package/build/src/services.d.ts +2 -1
  42. package/build/src/services.d.ts.map +1 -1
  43. package/build/src/services.js +26 -0
  44. package/package.json +5 -1
  45. package/src/index.ts +7 -0
  46. package/src/lib/plist/binary-plist-creator.ts +30 -0
  47. package/src/lib/plist/index.ts +2 -0
  48. package/src/lib/plist/plist-uid.ts +9 -0
  49. package/src/lib/types.ts +192 -1
  50. package/src/services/ios/dvt/channel-fragmenter.ts +42 -0
  51. package/src/services/ios/dvt/channel.ts +58 -0
  52. package/src/services/ios/dvt/dtx-message.ts +162 -0
  53. package/src/services/ios/dvt/index.ts +727 -0
  54. package/src/services/ios/dvt/instruments/condition-inducer.ts +140 -0
  55. package/src/services/ios/dvt/instruments/location-simulation.ts +83 -0
  56. package/src/services/ios/dvt/instruments/screenshot.ts +47 -0
  57. package/src/services/ios/dvt/nskeyedarchiver-decoder.ts +225 -0
  58. package/src/services/ios/dvt/utils.ts +89 -0
  59. package/src/services.ts +36 -0
@@ -0,0 +1,9 @@
1
+ import type { IPlistUID } from '../types.js';
2
+
3
+ /**
4
+ * UID (Unique Identifier) class for plist references
5
+ * Used in NSKeyedArchiver format
6
+ */
7
+ export class PlistUID implements IPlistUID {
8
+ constructor(public readonly value: number) {}
9
+ }
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 { LocationCoordinates } from '../services/ios/dvt/instruments/location-simulation.js';
9
10
  import { ProvisioningProfile } from '../services/ios/misagent/provisioning-profile.js';
10
11
  import type { PowerAssertionOptions } from '../services/ios/power-assertion/index.js';
11
12
  import { PowerAssertionType } from '../services/ios/power-assertion/index.js';
@@ -17,8 +18,13 @@ export type { PowerAssertionOptions };
17
18
  export { PowerAssertionType };
18
19
 
19
20
  /**
20
- * Represents a value that can be stored in a plist
21
+ * UID (Unique Identifier) interface for plist references
22
+ * Used in NSKeyedArchiver format
21
23
  */
24
+ export interface IPlistUID {
25
+ value: number;
26
+ }
27
+
22
28
  export type PlistValue =
23
29
  | string
24
30
  | number
@@ -26,6 +32,7 @@ export type PlistValue =
26
32
  | boolean
27
33
  | Date
28
34
  | Buffer
35
+ | IPlistUID
29
36
  | PlistArray
30
37
  | PlistDictionary
31
38
  | null;
@@ -347,6 +354,190 @@ export interface PowerAssertionServiceWithConnection {
347
354
  remoteXPC: RemoteXpcConnection;
348
355
  }
349
356
 
357
+ /**
358
+ * DVT (Developer Tools) service interface
359
+ */
360
+ export interface DVTSecureSocketProxyService extends BaseService {
361
+ /**
362
+ * Connect to the DVT service
363
+ */
364
+ connect(): Promise<void>;
365
+
366
+ /**
367
+ * Get supported identifiers (capabilities)
368
+ * @example
369
+ * const capabilities = dvtService.getSupportedIdentifiers();
370
+ * // Example output:
371
+ * // {
372
+ * // "com.apple.instruments.server.services.processcontrol.capability.memorylimits": 1,
373
+ * // "com.apple.instruments.server.services.coreml.perfrunner": 4,
374
+ * // "com.apple.instruments.server.services.processcontrolbydictionary": 4,
375
+ * // "com.apple.instruments.server.services.graphics.coreanimation.immediate": 1,
376
+ * // // ... more identifiers
377
+ * // }
378
+ */
379
+ getSupportedIdentifiers(): PlistDictionary;
380
+
381
+ /**
382
+ * Create a channel for a specific identifier
383
+ * @param identifier The channel identifier
384
+ * @returns The created channel
385
+ */
386
+ makeChannel(identifier: string): Promise<any>;
387
+
388
+ /**
389
+ * Close the DVT service connection
390
+ */
391
+ close(): Promise<void>;
392
+ }
393
+
394
+ /**
395
+ * Location simulation service interface
396
+ */
397
+ export interface LocationSimulationService {
398
+ /**
399
+ * Set the simulated location
400
+ * @param latitude The latitude
401
+ * @param longitude The longitude
402
+ */
403
+ setLocation(latitude: number, longitude: number): Promise<void>;
404
+
405
+ /**
406
+ * Set the simulated location using the LocationCoordinates type.
407
+ * @param coordinates The location coordinates
408
+ */
409
+ set(coordinates: LocationCoordinates): Promise<void>;
410
+
411
+ /**
412
+ * Clear/stop location simulation
413
+ *
414
+ * Note: This method is safe to call even if no location simulation is currently active.
415
+ */
416
+ clear(): Promise<void>;
417
+ }
418
+
419
+ /**
420
+ * Condition profile information
421
+ */
422
+ export interface ConditionProfile {
423
+ identifier: string;
424
+ description?: string;
425
+ [key: string]: any;
426
+ }
427
+
428
+ /**
429
+ * Condition group information
430
+ */
431
+ export interface ConditionGroup {
432
+ identifier: string;
433
+ profiles: ConditionProfile[];
434
+ [key: string]: any;
435
+ }
436
+
437
+ /**
438
+ * Condition inducer service interface
439
+ */
440
+ export interface ConditionInducerService {
441
+ /**
442
+ * List all available condition inducers and their profiles
443
+ *
444
+ * Each group in the response contains information about whether a condition
445
+ * is currently active via the `isActive` field and which profile is active
446
+ * via the `activeProfile` field.
447
+ *
448
+ * @returns Array of condition groups with their available profiles
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * const groups = await conditionInducer.list();
453
+ * // Example response:
454
+ * // [
455
+ * // {
456
+ * // "profiles": [
457
+ * // {
458
+ * // "name": "100% packet loss",
459
+ * // "identifier": "SlowNetwork100PctLoss",
460
+ * // "description": "Name: 100% Loss Scenario\nDownlink Bandwidth: 0 Mbps\nDownlink Latency: 0 ms\nDownlink Packet Loss Ratio: 100%\nUplink Bandwidth: 0 Mbps\nUplink Latency: 0 ms\nUplink Packet Loss Ratio: 100%"
461
+ * // },
462
+ * // // ... more profiles
463
+ * // ],
464
+ * // "profilesSorted": true,
465
+ * // "identifier": "SlowNetworkCondition",
466
+ * // "isDestructive": false,
467
+ * // "isInternal": false,
468
+ * // "activeProfile": "",
469
+ * // "name": "Network Link",
470
+ * // "isActive": false
471
+ * // },
472
+ * // // ... more groups
473
+ * // ]
474
+ * ```
475
+ */
476
+ list(): Promise<ConditionGroup[]>;
477
+
478
+ /**
479
+ * Set a specific condition profile
480
+ *
481
+ * Note: If a condition is already active, attempting to set a new one will
482
+ * throw an error: {'NSLocalizedDescription': 'A condition is already active.'}
483
+ * You must call disable() first before setting a different condition.
484
+ *
485
+ * Available profile identifiers include (but may vary by iOS version):
486
+ * - Network profiles: SlowNetwork100PctLoss, SlowNetworkVeryBadNetwork,
487
+ * SlowNetworkEdgeBad, SlowNetworkEdgeAverage, SlowNetworkEdgeGood,
488
+ * SlowNetworkEdge, SlowNetwork2GRural, SlowNetwork2GUrban,
489
+ * SlowNetwork3GAverage, SlowNetwork3GGood, SlowNetwork3G,
490
+ * SlowNetworkLTE, SlowNetworkWiFi, SlowNetworkWiFi80211AC,
491
+ * SlowNetworkDSL, SlowNetworkHighLatencyDNS
492
+ * - Thermal profiles: ThermalFair, ThermalSerious, ThermalCritical
493
+ * - GPU profiles: GPUPerformanceStateMin, GPUPerformanceStateMid, GPUPerformanceStateMax
494
+ * - And others depending on device capabilities
495
+ *
496
+ * Use list() to see all available profiles for your device.
497
+ *
498
+ * @param profileIdentifier The identifier of the profile to enable
499
+ * @throws Error if the profile identifier is not found
500
+ * @throws Error if a condition is already active
501
+ */
502
+ set(profileIdentifier: string): Promise<void>;
503
+
504
+ /**
505
+ * Disable the currently active condition
506
+ *
507
+ * Note: This method is idempotent - calling it when no condition is active
508
+ * will not throw an error.
509
+ */
510
+ disable(): Promise<void>;
511
+ }
512
+
513
+ /**
514
+ * Screenshot service interface
515
+ */
516
+ export interface ScreenshotService {
517
+ /**
518
+ * Capture a screenshot from the device
519
+ * @returns The screenshot data as a Buffer in PNG format, unscaled with the same dimensions as the device resolution
520
+ */
521
+ getScreenshot(): Promise<Buffer>;
522
+ }
523
+
524
+ /**
525
+ * DVT service with connection
526
+ * This allows callers to properly manage the connection lifecycle
527
+ */
528
+ export interface DVTServiceWithConnection {
529
+ /** The DVTSecureSocketProxyService instance */
530
+ dvtService: DVTSecureSocketProxyService;
531
+ /** The LocationSimulation service instance */
532
+ locationSimulation: LocationSimulationService;
533
+ /** The ConditionInducer service instance */
534
+ conditionInducer: ConditionInducerService;
535
+ /** The Screenshot service instance */
536
+ screenshot: ScreenshotService;
537
+ /** The RemoteXPC connection that can be used to close the connection */
538
+ remoteXPC: RemoteXpcConnection;
539
+ }
540
+
350
541
  /**
351
542
  * Represents the WebInspectorService
352
543
  */
@@ -0,0 +1,42 @@
1
+ import type { DTXMessageHeader } from './dtx-message.js';
2
+
3
+ /**
4
+ * Handles message fragmentation for DTX channels
5
+ * Assembles fragmented messages and queues complete messages for retrieval
6
+ */
7
+ export class ChannelFragmenter {
8
+ private readonly messages: Buffer[] = [];
9
+ private packetData: Buffer = Buffer.alloc(0);
10
+ private streamPacketData: Buffer = Buffer.alloc(0);
11
+
12
+ /**
13
+ * Get the next complete message from the queue
14
+ */
15
+ get(): Buffer | null {
16
+ return this.messages.shift() || null;
17
+ }
18
+
19
+ /**
20
+ * Add a message fragment and assemble if complete
21
+ * @param header The message header
22
+ * @param chunk The message data chunk
23
+ */
24
+ addFragment(header: DTXMessageHeader, chunk: Buffer): void {
25
+ // Handle positive vs negative channel codes (regular vs stream data)
26
+ if (header.channelCode >= 0) {
27
+ this.packetData = Buffer.concat([this.packetData, chunk]);
28
+
29
+ if (header.fragmentId === header.fragmentCount - 1) {
30
+ this.messages.push(this.packetData);
31
+ this.packetData = Buffer.alloc(0);
32
+ }
33
+ } else {
34
+ this.streamPacketData = Buffer.concat([this.streamPacketData, chunk]);
35
+
36
+ if (header.fragmentId === header.fragmentCount - 1) {
37
+ this.messages.push(this.streamPacketData);
38
+ this.streamPacketData = Buffer.alloc(0);
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,58 @@
1
+ import type { MessageAux } from './dtx-message.js';
2
+ import type { DVTSecureSocketProxyService } from './index.js';
3
+
4
+ export type ChannelMethodCall = (
5
+ args?: MessageAux,
6
+ expectsReply?: boolean,
7
+ ) => Promise<void>;
8
+
9
+ /**
10
+ * Represents a DTX communication channel for a specific instrument service
11
+ */
12
+ export class Channel {
13
+ constructor(
14
+ private readonly channelCode: number,
15
+ private readonly service: DVTSecureSocketProxyService,
16
+ ) {}
17
+
18
+ /**
19
+ * Receive a plist response from the channel
20
+ */
21
+ async receivePlist(): Promise<any> {
22
+ const [data] = await this.service.recvPlist(this.channelCode);
23
+ return data;
24
+ }
25
+
26
+ /**
27
+ * Call a method on this channel with automatic ObjectiveC selector conversion
28
+ *
29
+ * Converts method names to ObjectiveC selector format:
30
+ * - 'methodName' -> 'methodName'
31
+ * - 'method_name' -> 'method:name:'
32
+ * - '_method_name' -> '_method:name:'
33
+ *
34
+ * @param methodName The method name
35
+ * @returns A function that sends the message with optional arguments
36
+ */
37
+ call(methodName: string): ChannelMethodCall {
38
+ const selector = this.convertToSelector(methodName);
39
+ return (async (args, expectsReply = true) => {
40
+ await this.service.sendMessage(
41
+ this.channelCode,
42
+ selector,
43
+ args,
44
+ expectsReply,
45
+ );
46
+ }) as ChannelMethodCall;
47
+ }
48
+
49
+ /**
50
+ * Convert method name to ObjectiveC selector format
51
+ */
52
+ private convertToSelector(name: string): string {
53
+ if (name.startsWith('_')) {
54
+ return '_' + name.substring(1).replace(/_/g, ':');
55
+ }
56
+ return name.replace(/_/g, ':');
57
+ }
58
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * DTX Message Header structure
3
+ */
4
+ export interface DTXMessageHeader {
5
+ magic: number;
6
+ cb: number;
7
+ fragmentId: number;
8
+ fragmentCount: number;
9
+ length: number;
10
+ identifier: number;
11
+ conversationIndex: number;
12
+ channelCode: number;
13
+ expectsReply: number;
14
+ }
15
+
16
+ /**
17
+ * DTX Message Payload Header structure
18
+ */
19
+ export interface DTXMessagePayloadHeader {
20
+ flags: number;
21
+ auxiliaryLength: number;
22
+ totalLength: bigint;
23
+ }
24
+
25
+ /**
26
+ * Message auxiliary value structure
27
+ */
28
+ export interface MessageAuxValue {
29
+ type: number;
30
+ value: any;
31
+ }
32
+
33
+ /**
34
+ * DTX Protocol constants
35
+ */
36
+ export const DTX_CONSTANTS = {
37
+ MESSAGE_HEADER_MAGIC: 0x1f3d5b79,
38
+ MESSAGE_HEADER_SIZE: 32,
39
+ PAYLOAD_HEADER_SIZE: 16,
40
+ MESSAGE_AUX_MAGIC: 0x1f0,
41
+ EMPTY_DICTIONARY: 0xa,
42
+
43
+ // Message types
44
+ INSTRUMENTS_MESSAGE_TYPE: 2,
45
+ EXPECTS_REPLY_MASK: 0x1000,
46
+
47
+ // Auxiliary value types
48
+ AUX_TYPE_OBJECT: 2,
49
+ AUX_TYPE_INT32: 3,
50
+ AUX_TYPE_INT64: 6,
51
+ } as const;
52
+
53
+ /**
54
+ * DTX Message utilities for encoding and decoding protocol messages
55
+ */
56
+ export class DTXMessage {
57
+ /**
58
+ * Parse DTX message header from buffer
59
+ */
60
+ static parseMessageHeader(buffer: Buffer): DTXMessageHeader {
61
+ if (buffer.length < DTX_CONSTANTS.MESSAGE_HEADER_SIZE) {
62
+ throw new Error('Buffer too small for DTX message header');
63
+ }
64
+
65
+ return {
66
+ magic: buffer.readUInt32LE(0),
67
+ cb: buffer.readUInt32LE(4),
68
+ fragmentId: buffer.readUInt16LE(8),
69
+ fragmentCount: buffer.readUInt16LE(10),
70
+ length: buffer.readUInt32LE(12),
71
+ identifier: buffer.readUInt32LE(16),
72
+ conversationIndex: buffer.readUInt32LE(20),
73
+ channelCode: buffer.readInt32LE(24),
74
+ expectsReply: buffer.readUInt32LE(28),
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Build DTX message header buffer
80
+ */
81
+ static buildMessageHeader(header: DTXMessageHeader): Buffer {
82
+ const buffer = Buffer.alloc(DTX_CONSTANTS.MESSAGE_HEADER_SIZE);
83
+
84
+ buffer.writeUInt32LE(header.magic, 0);
85
+ buffer.writeUInt32LE(header.cb, 4);
86
+ buffer.writeUInt16LE(header.fragmentId, 8);
87
+ buffer.writeUInt16LE(header.fragmentCount, 10);
88
+ buffer.writeUInt32LE(header.length, 12);
89
+ buffer.writeUInt32LE(header.identifier, 16);
90
+ buffer.writeUInt32LE(header.conversationIndex, 20);
91
+ buffer.writeInt32LE(header.channelCode, 24);
92
+ buffer.writeUInt32LE(header.expectsReply, 28);
93
+
94
+ return buffer;
95
+ }
96
+
97
+ /**
98
+ * Parse DTX payload header from buffer
99
+ */
100
+ static parsePayloadHeader(buffer: Buffer): DTXMessagePayloadHeader {
101
+ if (buffer.length < DTX_CONSTANTS.PAYLOAD_HEADER_SIZE) {
102
+ throw new Error('Buffer too small for DTX payload header');
103
+ }
104
+
105
+ return {
106
+ flags: buffer.readUInt32LE(0),
107
+ auxiliaryLength: buffer.readUInt32LE(4),
108
+ totalLength: buffer.readBigUInt64LE(8),
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Build DTX payload header buffer
114
+ */
115
+ static buildPayloadHeader(header: DTXMessagePayloadHeader): Buffer {
116
+ const buffer = Buffer.alloc(DTX_CONSTANTS.PAYLOAD_HEADER_SIZE);
117
+
118
+ buffer.writeUInt32LE(header.flags, 0);
119
+ buffer.writeUInt32LE(header.auxiliaryLength, 4);
120
+ buffer.writeBigUInt64LE(header.totalLength, 8);
121
+
122
+ return buffer;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Message auxiliary builder for DTX protocol parameters
128
+ */
129
+ export class MessageAux {
130
+ private readonly values: MessageAuxValue[] = [];
131
+
132
+ /**
133
+ * Append a 32-bit integer
134
+ */
135
+ appendInt(value: number): MessageAux {
136
+ this.values.push({ type: DTX_CONSTANTS.AUX_TYPE_INT32, value });
137
+ return this;
138
+ }
139
+
140
+ /**
141
+ * Append a 64-bit integer (bigint)
142
+ */
143
+ appendLong(value: bigint): MessageAux {
144
+ this.values.push({ type: DTX_CONSTANTS.AUX_TYPE_INT64, value });
145
+ return this;
146
+ }
147
+
148
+ /**
149
+ * Append an object (will be archived as NSKeyedArchiver plist)
150
+ */
151
+ appendObj(value: any): MessageAux {
152
+ this.values.push({ type: DTX_CONSTANTS.AUX_TYPE_OBJECT, value });
153
+ return this;
154
+ }
155
+
156
+ /**
157
+ * Get raw values for encoding
158
+ */
159
+ getValues(): MessageAuxValue[] {
160
+ return this.values;
161
+ }
162
+ }