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,43 @@
1
+ import type { DVTSecureSocketProxyService } from '../index.js';
2
+ /**
3
+ * Geographic coordinates
4
+ */
5
+ export interface LocationCoordinates {
6
+ latitude: number;
7
+ longitude: number;
8
+ }
9
+ /**
10
+ * Location simulation service for simulating device GPS location
11
+ */
12
+ export declare class LocationSimulation {
13
+ private readonly dvt;
14
+ static readonly IDENTIFIER = "com.apple.instruments.server.services.LocationSimulation";
15
+ private channel;
16
+ constructor(dvt: DVTSecureSocketProxyService);
17
+ /**
18
+ * Initialize the location simulation channel
19
+ */
20
+ initialize(): Promise<void>;
21
+ /**
22
+ * Set the simulated GPS location
23
+ * @param coordinates The location coordinates
24
+ */
25
+ set(coordinates: LocationCoordinates): Promise<void>;
26
+ /**
27
+ * Set the simulated GPS location
28
+ * @param latitude The latitude coordinate
29
+ * @param longitude The longitude coordinate
30
+ */
31
+ setLocation(latitude: number, longitude: number): Promise<void>;
32
+ /**
33
+ * Stop location simulation and restore the actual device location
34
+ *
35
+ * Note: This method is safe to call even if no location simulation is currently active.
36
+ */
37
+ clear(): Promise<void>;
38
+ /**
39
+ * Stop location simulation (alias for clear)
40
+ */
41
+ stop(): Promise<void>;
42
+ }
43
+ //# sourceMappingURL=location-simulation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"location-simulation.d.ts","sourceRoot":"","sources":["../../../../../../src/services/ios/dvt/instruments/location-simulation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAI/D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAMjB,OAAO,CAAC,QAAQ,CAAC,GAAG;IALhC,MAAM,CAAC,QAAQ,CAAC,UAAU,8DACmC;IAE7D,OAAO,CAAC,OAAO,CAAwB;gBAEV,GAAG,EAAE,2BAA2B;IAE7D;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAQjC;;;OAGG;IACG,GAAG,CAAC,WAAW,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;;;OAIG;IACG,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrE;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
@@ -0,0 +1,60 @@
1
+ import { getLogger } from '../../../../lib/logger.js';
2
+ import { MessageAux } from '../dtx-message.js';
3
+ const log = getLogger('LocationSimulation');
4
+ /**
5
+ * Location simulation service for simulating device GPS location
6
+ */
7
+ export class LocationSimulation {
8
+ dvt;
9
+ static IDENTIFIER = 'com.apple.instruments.server.services.LocationSimulation';
10
+ channel = null;
11
+ constructor(dvt) {
12
+ this.dvt = dvt;
13
+ }
14
+ /**
15
+ * Initialize the location simulation channel
16
+ */
17
+ async initialize() {
18
+ if (this.channel) {
19
+ return;
20
+ }
21
+ this.channel = await this.dvt.makeChannel(LocationSimulation.IDENTIFIER);
22
+ }
23
+ /**
24
+ * Set the simulated GPS location
25
+ * @param coordinates The location coordinates
26
+ */
27
+ async set(coordinates) {
28
+ await this.initialize();
29
+ const args = new MessageAux()
30
+ .appendObj(coordinates.latitude)
31
+ .appendObj(coordinates.longitude);
32
+ await this.channel.call('simulateLocationWithLatitude_longitude_')(args);
33
+ await this.channel.receivePlist();
34
+ log.info(`Location set to: ${coordinates.latitude}, ${coordinates.longitude}`);
35
+ }
36
+ /**
37
+ * Set the simulated GPS location
38
+ * @param latitude The latitude coordinate
39
+ * @param longitude The longitude coordinate
40
+ */
41
+ async setLocation(latitude, longitude) {
42
+ await this.set({ latitude, longitude });
43
+ }
44
+ /**
45
+ * Stop location simulation and restore the actual device location
46
+ *
47
+ * Note: This method is safe to call even if no location simulation is currently active.
48
+ */
49
+ async clear() {
50
+ await this.initialize();
51
+ await this.channel.call('stopLocationSimulation')();
52
+ log.info('Location simulation stopped');
53
+ }
54
+ /**
55
+ * Stop location simulation (alias for clear)
56
+ */
57
+ async stop() {
58
+ await this.clear();
59
+ }
60
+ }
@@ -0,0 +1,17 @@
1
+ import type { DVTSecureSocketProxyService } from '../index.js';
2
+ /**
3
+ * Screenshot service for capturing device screenshots
4
+ */
5
+ export declare class Screenshot {
6
+ private readonly dvt;
7
+ static readonly IDENTIFIER = "com.apple.instruments.server.services.screenshot";
8
+ private channel;
9
+ constructor(dvt: DVTSecureSocketProxyService);
10
+ initialize(): Promise<void>;
11
+ /**
12
+ * Capture a screenshot from the device
13
+ * @returns The screenshot data as a Buffer
14
+ */
15
+ getScreenshot(): Promise<Buffer>;
16
+ }
17
+ //# sourceMappingURL=screenshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../../../../../src/services/ios/dvt/instruments/screenshot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AAI/D;;GAEG;AACH,qBAAa,UAAU;IAMT,OAAO,CAAC,QAAQ,CAAC,GAAG;IALhC,MAAM,CAAC,QAAQ,CAAC,UAAU,sDAC2B;IAErD,OAAO,CAAC,OAAO,CAAwB;gBAEV,GAAG,EAAE,2BAA2B;IAEvD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;;OAGG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;CAmBvC"}
@@ -0,0 +1,35 @@
1
+ import { getLogger } from '../../../../lib/logger.js';
2
+ const log = getLogger('Screenshot');
3
+ /**
4
+ * Screenshot service for capturing device screenshots
5
+ */
6
+ export class Screenshot {
7
+ dvt;
8
+ static IDENTIFIER = 'com.apple.instruments.server.services.screenshot';
9
+ channel = null;
10
+ constructor(dvt) {
11
+ this.dvt = dvt;
12
+ }
13
+ async initialize() {
14
+ if (!this.channel) {
15
+ this.channel = await this.dvt.makeChannel(Screenshot.IDENTIFIER);
16
+ }
17
+ }
18
+ /**
19
+ * Capture a screenshot from the device
20
+ * @returns The screenshot data as a Buffer
21
+ */
22
+ async getScreenshot() {
23
+ await this.initialize();
24
+ await this.channel.call('takeScreenshot')();
25
+ const result = await this.channel.receivePlist();
26
+ if (!result) {
27
+ throw new Error('Failed to capture screenshot: received null response');
28
+ }
29
+ if (!Buffer.isBuffer(result)) {
30
+ throw new Error(`Unexpected response format from getScreenshot: expected Buffer, got ${typeof result}`);
31
+ }
32
+ log.info(`Screenshot captured successfully (${result.length} bytes)`);
33
+ return result;
34
+ }
35
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Decode NSKeyedArchiver formatted data into native JavaScript objects
3
+ *
4
+ * NSKeyedArchiver is Apple's serialization format that stores object graphs
5
+ * with references. The format includes:
6
+ * - $version: Archive version (typically 100000)
7
+ * - $archiver: "NSKeyedArchiver"
8
+ * - $top: Root object references
9
+ * - $objects: Array of all objects with cross-references
10
+ */
11
+ export declare class NSKeyedArchiverDecoder {
12
+ private readonly objects;
13
+ private readonly decoded;
14
+ private readonly archive;
15
+ constructor(data: any);
16
+ /**
17
+ * Check if data is in NSKeyedArchiver format
18
+ */
19
+ static isNSKeyedArchive(data: any): boolean;
20
+ /**
21
+ * Decode the entire archive starting from the root
22
+ */
23
+ decode(): any;
24
+ /**
25
+ * Decode an object at a specific index
26
+ */
27
+ private decodeObject;
28
+ /**
29
+ * Decode an NSArray
30
+ */
31
+ private decodeArray;
32
+ /**
33
+ * Decode an NSDictionary
34
+ */
35
+ private decodeDictionary;
36
+ }
37
+ /**
38
+ * Decode NSKeyedArchiver data or return as-is if not archived
39
+ */
40
+ export declare function decodeNSKeyedArchiver(data: any): any;
41
+ //# sourceMappingURL=nskeyedarchiver-decoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nskeyedarchiver-decoder.d.ts","sourceRoot":"","sources":["../../../../../src/services/ios/dvt/nskeyedarchiver-decoder.ts"],"names":[],"mappings":"AAIA;;;;;;;;;GASG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAM;gBAElB,IAAI,EAAE,GAAG;IAUrB;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO;IAa3C;;OAEG;IACH,MAAM,IAAI,GAAG;IA8Bb;;OAEG;IACH,OAAO,CAAC,YAAY;IAkFpB;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAkBzB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAiBpD"}
@@ -0,0 +1,195 @@
1
+ import { getLogger } from '../../../lib/logger.js';
2
+ const log = getLogger('NSKeyedArchiverDecoder');
3
+ /**
4
+ * Decode NSKeyedArchiver formatted data into native JavaScript objects
5
+ *
6
+ * NSKeyedArchiver is Apple's serialization format that stores object graphs
7
+ * with references. The format includes:
8
+ * - $version: Archive version (typically 100000)
9
+ * - $archiver: "NSKeyedArchiver"
10
+ * - $top: Root object references
11
+ * - $objects: Array of all objects with cross-references
12
+ */
13
+ export class NSKeyedArchiverDecoder {
14
+ objects;
15
+ decoded;
16
+ archive;
17
+ constructor(data) {
18
+ if (!NSKeyedArchiverDecoder.isNSKeyedArchive(data)) {
19
+ throw new Error('Data is not in NSKeyedArchiver format');
20
+ }
21
+ this.archive = data;
22
+ this.objects = data.$objects || [];
23
+ this.decoded = new Map();
24
+ }
25
+ /**
26
+ * Check if data is in NSKeyedArchiver format
27
+ */
28
+ static isNSKeyedArchive(data) {
29
+ if (!data || typeof data !== 'object') {
30
+ return false;
31
+ }
32
+ return ('$archiver' in data &&
33
+ data.$archiver === 'NSKeyedArchiver' &&
34
+ '$objects' in data &&
35
+ Array.isArray(data.$objects));
36
+ }
37
+ /**
38
+ * Decode the entire archive starting from the root
39
+ */
40
+ decode() {
41
+ if (!this.objects || this.objects.length === 0) {
42
+ return null;
43
+ }
44
+ // Extract root reference from $top
45
+ let rootIndex = null;
46
+ if (this.archive.$top && typeof this.archive.$top === 'object') {
47
+ const top = this.archive.$top;
48
+ if ('root' in top) {
49
+ const root = top.root;
50
+ if (typeof root === 'number') {
51
+ rootIndex = root;
52
+ }
53
+ else if (typeof root === 'object' && root && 'CF$UID' in root) {
54
+ rootIndex = root.CF$UID;
55
+ }
56
+ }
57
+ }
58
+ // If we found the root index, decode it
59
+ if (rootIndex !== null) {
60
+ return this.decodeObject(rootIndex);
61
+ }
62
+ // Fallback: decode first non-null object
63
+ log.warn('Could not find root reference, using fallback');
64
+ return this.decodeObject(1);
65
+ }
66
+ /**
67
+ * Decode an object at a specific index
68
+ */
69
+ decodeObject(index) {
70
+ if (index < 0 || index >= this.objects.length) {
71
+ return null;
72
+ }
73
+ // Check cache
74
+ if (this.decoded.has(index)) {
75
+ return this.decoded.get(index);
76
+ }
77
+ const obj = this.objects[index];
78
+ // Handle null marker
79
+ if (obj === '$null' || obj === null) {
80
+ return null;
81
+ }
82
+ // Handle primitive types
83
+ if (typeof obj !== 'object') {
84
+ return obj;
85
+ }
86
+ // Handle Buffer/binary data (eg. screenshots)
87
+ if (Buffer.isBuffer(obj)) {
88
+ this.decoded.set(index, obj);
89
+ return obj;
90
+ }
91
+ // Handle UID references
92
+ if ('CF$UID' in obj) {
93
+ return this.decodeObject(obj.CF$UID);
94
+ }
95
+ // Handle NSDictionary (NS.keys + NS.objects) - check this FIRST before NSArray
96
+ if ('NS.keys' in obj && 'NS.objects' in obj) {
97
+ const result = this.decodeDictionary(obj['NS.keys'], obj['NS.objects']);
98
+ this.decoded.set(index, result);
99
+ return result;
100
+ }
101
+ // Handle NSArray (NS.objects only, without NS.keys)
102
+ if ('NS.objects' in obj) {
103
+ const result = this.decodeArray(obj['NS.objects']);
104
+ this.decoded.set(index, result);
105
+ return result;
106
+ }
107
+ // Handle regular objects - just return as-is but resolve references
108
+ const result = {};
109
+ for (const [key, value] of Object.entries(obj)) {
110
+ if (key === '$class') {
111
+ continue; // Skip class metadata
112
+ }
113
+ if (typeof value === 'number') {
114
+ // Could be a reference or primitive
115
+ const referenced = this.objects[value];
116
+ if (referenced &&
117
+ typeof referenced === 'object' &&
118
+ referenced !== '$null') {
119
+ result[key] = this.decodeObject(value);
120
+ }
121
+ else {
122
+ result[key] = value;
123
+ }
124
+ }
125
+ else if (typeof value === 'object' && value && 'CF$UID' in value) {
126
+ const uid = value.CF$UID;
127
+ if (typeof uid === 'number') {
128
+ result[key] = this.decodeObject(uid);
129
+ }
130
+ else {
131
+ result[key] = value;
132
+ }
133
+ }
134
+ else {
135
+ result[key] = value;
136
+ }
137
+ }
138
+ this.decoded.set(index, result);
139
+ return result;
140
+ }
141
+ /**
142
+ * Decode an NSArray
143
+ */
144
+ decodeArray(refs) {
145
+ if (!Array.isArray(refs)) {
146
+ return [];
147
+ }
148
+ return refs.map((ref) => {
149
+ if (typeof ref === 'number') {
150
+ return this.decodeObject(ref);
151
+ }
152
+ else if (typeof ref === 'object' && ref && 'CF$UID' in ref) {
153
+ return this.decodeObject(ref.CF$UID);
154
+ }
155
+ return ref;
156
+ });
157
+ }
158
+ /**
159
+ * Decode an NSDictionary
160
+ */
161
+ decodeDictionary(keyRefs, valueRefs) {
162
+ if (!Array.isArray(keyRefs) || !Array.isArray(valueRefs)) {
163
+ return {};
164
+ }
165
+ const result = {};
166
+ for (let i = 0; i < keyRefs.length && i < valueRefs.length; i++) {
167
+ const key = this.decodeObject(keyRefs[i]);
168
+ const value = this.decodeObject(valueRefs[i]);
169
+ if (typeof key === 'string') {
170
+ result[key] = value;
171
+ }
172
+ }
173
+ return result;
174
+ }
175
+ }
176
+ /**
177
+ * Decode NSKeyedArchiver data or return as-is if not archived
178
+ */
179
+ export function decodeNSKeyedArchiver(data) {
180
+ if (!data) {
181
+ return data;
182
+ }
183
+ // Check if this is NSKeyedArchiver format
184
+ if (!NSKeyedArchiverDecoder.isNSKeyedArchive(data)) {
185
+ return data;
186
+ }
187
+ try {
188
+ const decoder = new NSKeyedArchiverDecoder(data);
189
+ return decoder.decode();
190
+ }
191
+ catch (error) {
192
+ log.warn('Failed to decode NSKeyedArchiver data:', error);
193
+ return data;
194
+ }
195
+ }
@@ -0,0 +1,19 @@
1
+ import type { PlistDictionary } from '../../../lib/types.js';
2
+ export declare function isPlainObject(value: any): value is Record<string, any>;
3
+ export declare function hasProperties(obj: any, ...props: string[]): boolean;
4
+ export declare function isNSKeyedArchiverFormat(data: any): boolean;
5
+ export declare function isNSDictionaryFormat(obj: any): boolean;
6
+ export declare function hasNSErrorIndicators(obj: any): boolean;
7
+ /**
8
+ * Extract $objects array from NSKeyedArchiver format, returns null if invalid
9
+ */
10
+ export declare function extractNSKeyedArchiverObjects(data: any): any[] | null;
11
+ /**
12
+ * Extract NSDictionary from NSKeyedArchiver objects using key/value references
13
+ */
14
+ export declare function extractNSDictionary(dictObj: any, objects: any[]): PlistDictionary;
15
+ /**
16
+ * Extract strings from NSKeyedArchiver objects array as dictionary keys
17
+ */
18
+ export declare function extractCapabilityStrings(objects: any[]): PlistDictionary;
19
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../../src/services/ios/dvt/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAEtE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAEnE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,GAAG,GAAG,OAAO,CAM1D;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAEtD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAOtD;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,IAAI,CAOrE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE,GAAG,EAAE,GACb,eAAe,CAsBjB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,eAAe,CAYxE"}
@@ -0,0 +1,67 @@
1
+ export function isPlainObject(value) {
2
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
3
+ }
4
+ export function hasProperties(obj, ...props) {
5
+ return isPlainObject(obj) ? props.every((prop) => prop in obj) : false;
6
+ }
7
+ export function isNSKeyedArchiverFormat(data) {
8
+ return (hasProperties(data, '$objects') &&
9
+ Array.isArray(data.$objects) &&
10
+ data.$objects.length > 0);
11
+ }
12
+ export function isNSDictionaryFormat(obj) {
13
+ return hasProperties(obj, 'NS.keys', 'NS.objects');
14
+ }
15
+ export function hasNSErrorIndicators(obj) {
16
+ if (!isPlainObject(obj)) {
17
+ return false;
18
+ }
19
+ const errorProps = ['NSCode', 'NSUserInfo', 'NSDomain'];
20
+ return errorProps.some((prop) => prop in obj);
21
+ }
22
+ /**
23
+ * Extract $objects array from NSKeyedArchiver format, returns null if invalid
24
+ */
25
+ export function extractNSKeyedArchiverObjects(data) {
26
+ if (!isNSKeyedArchiverFormat(data)) {
27
+ return null;
28
+ }
29
+ const objects = data.$objects;
30
+ return objects.length > 1 ? objects : null;
31
+ }
32
+ /**
33
+ * Extract NSDictionary from NSKeyedArchiver objects using key/value references
34
+ */
35
+ export function extractNSDictionary(dictObj, objects) {
36
+ if (!isNSDictionaryFormat(dictObj)) {
37
+ return {};
38
+ }
39
+ const keysRef = dictObj['NS.keys'];
40
+ const valuesRef = dictObj['NS.objects'];
41
+ if (!Array.isArray(keysRef) || !Array.isArray(valuesRef)) {
42
+ return {};
43
+ }
44
+ const result = {};
45
+ for (let i = 0; i < keysRef.length; i++) {
46
+ const key = objects[keysRef[i]];
47
+ const value = objects[valuesRef[i]];
48
+ if (typeof key === 'string') {
49
+ result[key] = value;
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ /**
55
+ * Extract strings from NSKeyedArchiver objects array as dictionary keys
56
+ */
57
+ export function extractCapabilityStrings(objects) {
58
+ const result = {};
59
+ // Start from index 1 because index 0 is always '$null' in NSKeyedArchiver format
60
+ for (let i = 1; i < objects.length; i++) {
61
+ const obj = objects[i];
62
+ if (typeof obj === 'string' && obj !== '$null') {
63
+ result[obj] = true;
64
+ }
65
+ }
66
+ return result;
67
+ }
@@ -1,5 +1,5 @@
1
1
  import { RemoteXpcConnection } from './lib/remote-xpc/remote-xpc-connection.js';
2
- import type { DiagnosticsServiceWithConnection, MisagentServiceWithConnection, MobileConfigServiceWithConnection, MobileImageMounterServiceWithConnection, NotificationProxyServiceWithConnection, PowerAssertionServiceWithConnection, SpringboardServiceWithConnection, SyslogService as SyslogServiceType, WebInspectorServiceWithConnection } from './lib/types.js';
2
+ import type { DVTServiceWithConnection, DiagnosticsServiceWithConnection, MisagentServiceWithConnection, MobileConfigServiceWithConnection, MobileImageMounterServiceWithConnection, NotificationProxyServiceWithConnection, PowerAssertionServiceWithConnection, SpringboardServiceWithConnection, SyslogService as SyslogServiceType, WebInspectorServiceWithConnection } from './lib/types.js';
3
3
  import AfcService from './services/ios/afc/index.js';
4
4
  export declare function startDiagnosticsService(udid: string): Promise<DiagnosticsServiceWithConnection>;
5
5
  export declare function startNotificationProxyService(udid: string): Promise<NotificationProxyServiceWithConnection>;
@@ -15,6 +15,7 @@ export declare function startSyslogService(udid: string): Promise<SyslogServiceT
15
15
  */
16
16
  export declare function startAfcService(udid: string): Promise<AfcService>;
17
17
  export declare function startWebInspectorService(udid: string): Promise<WebInspectorServiceWithConnection>;
18
+ export declare function startDVTService(udid: string): Promise<DVTServiceWithConnection>;
18
19
  export declare function createRemoteXPCConnection(udid: string): Promise<{
19
20
  remoteXPC: RemoteXpcConnection;
20
21
  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,6BAA6B,EAC7B,iCAAiC,EACjC,uCAAuC,EACvC,sCAAsC,EACtC,mCAAmC,EACnC,gCAAgC,EAChC,aAAa,IAAI,iBAAiB,EAClC,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,UAAU,MAAM,6BAA6B,CAAC;AAcrD,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;AAED,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,oBAAoB,CACxC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,6BAA6B,CAAC,CAYxC;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mCAAmC,CAAC,CAY9C;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAOvE;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iCAAiC,CAAC,CAY5C;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,wBAAwB,EACxB,gCAAgC,EAChC,6BAA6B,EAC7B,iCAAiC,EACjC,uCAAuC,EACvC,sCAAsC,EACtC,mCAAmC,EACnC,gCAAgC,EAChC,aAAa,IAAI,iBAAiB,EAClC,iCAAiC,EAClC,MAAM,gBAAgB,CAAC;AACxB,OAAO,UAAU,MAAM,6BAA6B,CAAC;AAkBrD,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;AAED,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,oBAAoB,CACxC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,6BAA6B,CAAC,CAYxC;AAED,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mCAAmC,CAAC,CAY9C;AAED,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAG5B;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAOvE;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iCAAiC,CAAC,CAY5C;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,wBAAwB,CAAC,CA2BnC;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,MAAM;;;;;;;;GAO3D"}
@@ -4,6 +4,10 @@ import { TunnelManager } from './lib/tunnel/index.js';
4
4
  import { TunnelApiClient } from './lib/tunnel/tunnel-api-client.js';
5
5
  import AfcService from './services/ios/afc/index.js';
6
6
  import DiagnosticsService from './services/ios/diagnostic-service/index.js';
7
+ import { DVTSecureSocketProxyService } from './services/ios/dvt/index.js';
8
+ import { ConditionInducer } from './services/ios/dvt/instruments/condition-inducer.js';
9
+ import { LocationSimulation } from './services/ios/dvt/instruments/location-simulation.js';
10
+ import { Screenshot } from './services/ios/dvt/instruments/screenshot.js';
7
11
  import { MisagentService } from './services/ios/misagent/index.js';
8
12
  import { MobileConfigService } from './services/ios/mobile-config/index.js';
9
13
  import MobileImageMounterService from './services/ios/mobile-image-mounter/index.js';
@@ -118,6 +122,28 @@ export async function startWebInspectorService(udid) {
118
122
  ]),
119
123
  };
120
124
  }
125
+ export async function startDVTService(udid) {
126
+ const { remoteXPC, tunnelConnection } = await createRemoteXPCConnection(udid);
127
+ const dvtServiceDescriptor = remoteXPC.findService(DVTSecureSocketProxyService.RSD_SERVICE_NAME);
128
+ // Create DVT service instance
129
+ const dvtService = new DVTSecureSocketProxyService([
130
+ tunnelConnection.host,
131
+ parseInt(dvtServiceDescriptor.port, 10),
132
+ ]);
133
+ // Connect to DVT service
134
+ await dvtService.connect();
135
+ // Create instrument services
136
+ const locationSimulation = new LocationSimulation(dvtService);
137
+ const conditionInducer = new ConditionInducer(dvtService);
138
+ const screenshot = new Screenshot(dvtService);
139
+ return {
140
+ remoteXPC: remoteXPC,
141
+ dvtService,
142
+ locationSimulation,
143
+ conditionInducer,
144
+ screenshot,
145
+ };
146
+ }
121
147
  export async function createRemoteXPCConnection(udid) {
122
148
  const tunnelConnection = await getTunnelInformation(udid);
123
149
  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.13.2",
3
+ "version": "0.15.0",
4
4
  "main": "build/src/index.js",
5
5
  "types": "build/src/index.d.ts",
6
6
  "type": "module",
@@ -39,6 +39,10 @@
39
39
  "test:misagent": "mocha test/integration/misagent-service-test.ts --exit --timeout 1m",
40
40
  "test:afc": "mocha test/integration/afc-test.ts --exit --timeout 1m",
41
41
  "test:power-assertion": "mocha test/integration/power-assertion-test.ts --exit --timeout 1m",
42
+ "test:dvt": "mocha test/integration/dvt-service-test.ts --exit --timeout 1m",
43
+ "test:dvt:location-simulation": "mocha test/integration/dvt_instruments/location-simulation-test.ts --exit --timeout 1m",
44
+ "test:dvt:condition-inducer": "mocha test/integration/dvt_instruments/condition-inducer-test.ts --exit --timeout 1m",
45
+ "test:dvt:screenshot": "mocha test/integration/dvt_instruments/screenshot-test.ts --exit --timeout 1m",
42
46
  "test:tunnel-creation": "sudo tsx scripts/test-tunnel-creation.ts",
43
47
  "test:tunnel-creation:lsof": "sudo tsx scripts/test-tunnel-creation.ts --keep-open"
44
48
  },
package/src/index.ts CHANGED
@@ -24,6 +24,12 @@ export type {
24
24
  WebInspectorService,
25
25
  MisagentService,
26
26
  SyslogService,
27
+ DVTSecureSocketProxyService,
28
+ LocationSimulationService,
29
+ ConditionInducerService,
30
+ ScreenshotService,
31
+ ConditionProfile,
32
+ ConditionGroup,
27
33
  SocketInfo,
28
34
  TunnelResult,
29
35
  TunnelRegistry,
@@ -36,6 +42,7 @@ export type {
36
42
  SpringboardServiceWithConnection,
37
43
  WebInspectorServiceWithConnection,
38
44
  MisagentServiceWithConnection,
45
+ DVTServiceWithConnection,
39
46
  } from './lib/types.js';
40
47
  export { PowerAssertionType } from './lib/types.js';
41
48
  export {
@@ -11,6 +11,7 @@ import {
11
11
  BPLIST_TRAILER_SIZE,
12
12
  BPLIST_TYPE,
13
13
  } from './constants.js';
14
+ import { PlistUID } from './plist-uid.js';
14
15
 
15
16
  /**
16
17
  * Checks if a value is a plain object (not null, not an array, not a Date, not a Buffer)
@@ -220,6 +221,30 @@ class BinaryPlistCreator {
220
221
  return Buffer.from([value ? BPLIST_TYPE.TRUE : BPLIST_TYPE.FALSE]);
221
222
  }
222
223
 
224
+ /**
225
+ * Creates UID data for NSKeyedArchiver references
226
+ * @param value - The UID value to encode
227
+ * @returns Buffer containing the encoded UID
228
+ */
229
+ private _createUIDData(value: number): Buffer {
230
+ // UID format: 0x80 | length_marker, followed by the UID value
231
+ let buffer: Buffer;
232
+ if (value <= 255) {
233
+ buffer = Buffer.alloc(2);
234
+ buffer.writeUInt8(BPLIST_TYPE.UID | 0, 0);
235
+ buffer.writeUInt8(value, 1);
236
+ } else if (value <= 65535) {
237
+ buffer = Buffer.alloc(3);
238
+ buffer.writeUInt8(BPLIST_TYPE.UID | 1, 0);
239
+ buffer.writeUInt16BE(value, 1);
240
+ } else {
241
+ buffer = Buffer.alloc(5);
242
+ buffer.writeUInt8(BPLIST_TYPE.UID | 2, 0);
243
+ buffer.writeUInt32BE(value, 1);
244
+ }
245
+ return buffer;
246
+ }
247
+
223
248
  /**
224
249
  * Creates binary data for an integer value
225
250
  * @param value - The integer value (number or bigint)
@@ -507,6 +532,11 @@ class BinaryPlistCreator {
507
532
  return this._createBufferData(value);
508
533
  }
509
534
 
535
+ // Handle UID (for NSKeyedArchiver format)
536
+ if (value instanceof PlistUID) {
537
+ return this._createUIDData(value.value);
538
+ }
539
+
510
540
  // Handle strings
511
541
  if (typeof value === 'string') {
512
542
  return this._createStringData(value);
@@ -26,6 +26,8 @@ import {
26
26
  trimBeforeXmlDeclaration,
27
27
  } from './utils.js';
28
28
 
29
+ export { PlistUID } from './plist-uid.js';
30
+
29
31
  export {
30
32
  createPlist,
31
33
  createXmlPlist,