appium-ios-remotexpc 0.13.1 → 0.14.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.
- package/CHANGELOG.md +12 -0
- package/build/src/index.d.ts +1 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/lib/plist/binary-plist-creator.d.ts.map +1 -1
- package/build/src/lib/plist/binary-plist-creator.js +30 -0
- package/build/src/lib/plist/index.d.ts +1 -0
- package/build/src/lib/plist/index.d.ts.map +1 -1
- package/build/src/lib/plist/index.js +1 -0
- package/build/src/lib/plist/plist-uid.d.ts +10 -0
- package/build/src/lib/plist/plist-uid.d.ts.map +1 -0
- package/build/src/lib/plist/plist-uid.js +10 -0
- package/build/src/lib/types.d.ts +165 -2
- package/build/src/lib/types.d.ts.map +1 -1
- package/build/src/services/ios/dvt/channel-fragmenter.d.ts +21 -0
- package/build/src/services/ios/dvt/channel-fragmenter.d.ts.map +1 -0
- package/build/src/services/ios/dvt/channel-fragmenter.js +37 -0
- package/build/src/services/ios/dvt/channel.d.ts +32 -0
- package/build/src/services/ios/dvt/channel.d.ts.map +1 -0
- package/build/src/services/ios/dvt/channel.js +44 -0
- package/build/src/services/ios/dvt/dtx-message.d.ts +88 -0
- package/build/src/services/ios/dvt/dtx-message.d.ts.map +1 -0
- package/build/src/services/ios/dvt/dtx-message.js +113 -0
- package/build/src/services/ios/dvt/index.d.ts +119 -0
- package/build/src/services/ios/dvt/index.d.ts.map +1 -0
- package/build/src/services/ios/dvt/index.js +552 -0
- package/build/src/services/ios/dvt/instruments/condition-inducer.d.ts +37 -0
- package/build/src/services/ios/dvt/instruments/condition-inducer.d.ts.map +1 -0
- package/build/src/services/ios/dvt/instruments/condition-inducer.js +99 -0
- package/build/src/services/ios/dvt/instruments/location-simulation.d.ts +43 -0
- package/build/src/services/ios/dvt/instruments/location-simulation.d.ts.map +1 -0
- package/build/src/services/ios/dvt/instruments/location-simulation.js +60 -0
- package/build/src/services/ios/dvt/nskeyedarchiver-decoder.d.ts +41 -0
- package/build/src/services/ios/dvt/nskeyedarchiver-decoder.d.ts.map +1 -0
- package/build/src/services/ios/dvt/nskeyedarchiver-decoder.js +190 -0
- package/build/src/services/ios/dvt/utils.d.ts +19 -0
- package/build/src/services/ios/dvt/utils.d.ts.map +1 -0
- package/build/src/services/ios/dvt/utils.js +67 -0
- package/build/src/services.d.ts +2 -1
- package/build/src/services.d.ts.map +1 -1
- package/build/src/services.js +23 -0
- package/package.json +4 -1
- package/src/index.ts +6 -0
- package/src/lib/plist/binary-plist-creator.ts +30 -0
- package/src/lib/plist/index.ts +2 -0
- package/src/lib/plist/plist-uid.ts +9 -0
- package/src/lib/types.ts +179 -1
- package/src/services/ios/dvt/channel-fragmenter.ts +42 -0
- package/src/services/ios/dvt/channel.ts +58 -0
- package/src/services/ios/dvt/dtx-message.ts +162 -0
- package/src/services/ios/dvt/index.ts +727 -0
- package/src/services/ios/dvt/instruments/condition-inducer.ts +140 -0
- package/src/services/ios/dvt/instruments/location-simulation.ts +83 -0
- package/src/services/ios/dvt/nskeyedarchiver-decoder.ts +219 -0
- package/src/services/ios/dvt/utils.ts +89 -0
- package/src/services.ts +33 -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,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;IA4EpB;;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,190 @@
|
|
|
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 UID references
|
|
87
|
+
if ('CF$UID' in obj) {
|
|
88
|
+
return this.decodeObject(obj.CF$UID);
|
|
89
|
+
}
|
|
90
|
+
// Handle NSDictionary (NS.keys + NS.objects) - check this FIRST before NSArray
|
|
91
|
+
if ('NS.keys' in obj && 'NS.objects' in obj) {
|
|
92
|
+
const result = this.decodeDictionary(obj['NS.keys'], obj['NS.objects']);
|
|
93
|
+
this.decoded.set(index, result);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
// Handle NSArray (NS.objects only, without NS.keys)
|
|
97
|
+
if ('NS.objects' in obj) {
|
|
98
|
+
const result = this.decodeArray(obj['NS.objects']);
|
|
99
|
+
this.decoded.set(index, result);
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
// Handle regular objects - just return as-is but resolve references
|
|
103
|
+
const result = {};
|
|
104
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
105
|
+
if (key === '$class') {
|
|
106
|
+
continue; // Skip class metadata
|
|
107
|
+
}
|
|
108
|
+
if (typeof value === 'number') {
|
|
109
|
+
// Could be a reference or primitive
|
|
110
|
+
const referenced = this.objects[value];
|
|
111
|
+
if (referenced &&
|
|
112
|
+
typeof referenced === 'object' &&
|
|
113
|
+
referenced !== '$null') {
|
|
114
|
+
result[key] = this.decodeObject(value);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
result[key] = value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (typeof value === 'object' && value && 'CF$UID' in value) {
|
|
121
|
+
const uid = value.CF$UID;
|
|
122
|
+
if (typeof uid === 'number') {
|
|
123
|
+
result[key] = this.decodeObject(uid);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
result[key] = value;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
result[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this.decoded.set(index, result);
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Decode an NSArray
|
|
138
|
+
*/
|
|
139
|
+
decodeArray(refs) {
|
|
140
|
+
if (!Array.isArray(refs)) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
return refs.map((ref) => {
|
|
144
|
+
if (typeof ref === 'number') {
|
|
145
|
+
return this.decodeObject(ref);
|
|
146
|
+
}
|
|
147
|
+
else if (typeof ref === 'object' && ref && 'CF$UID' in ref) {
|
|
148
|
+
return this.decodeObject(ref.CF$UID);
|
|
149
|
+
}
|
|
150
|
+
return ref;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Decode an NSDictionary
|
|
155
|
+
*/
|
|
156
|
+
decodeDictionary(keyRefs, valueRefs) {
|
|
157
|
+
if (!Array.isArray(keyRefs) || !Array.isArray(valueRefs)) {
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
const result = {};
|
|
161
|
+
for (let i = 0; i < keyRefs.length && i < valueRefs.length; i++) {
|
|
162
|
+
const key = this.decodeObject(keyRefs[i]);
|
|
163
|
+
const value = this.decodeObject(valueRefs[i]);
|
|
164
|
+
if (typeof key === 'string') {
|
|
165
|
+
result[key] = value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Decode NSKeyedArchiver data or return as-is if not archived
|
|
173
|
+
*/
|
|
174
|
+
export function decodeNSKeyedArchiver(data) {
|
|
175
|
+
if (!data) {
|
|
176
|
+
return data;
|
|
177
|
+
}
|
|
178
|
+
// Check if this is NSKeyedArchiver format
|
|
179
|
+
if (!NSKeyedArchiverDecoder.isNSKeyedArchive(data)) {
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const decoder = new NSKeyedArchiverDecoder(data);
|
|
184
|
+
return decoder.decode();
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
log.warn('Failed to decode NSKeyedArchiver data:', error);
|
|
188
|
+
return data;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -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
|
+
}
|
package/build/src/services.d.ts
CHANGED
|
@@ -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;
|
|
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;AAiBrD,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,CAyBnC;AAED,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,MAAM;;;;;;;;GAO3D"}
|
package/build/src/services.js
CHANGED
|
@@ -4,6 +4,9 @@ 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';
|
|
7
10
|
import { MisagentService } from './services/ios/misagent/index.js';
|
|
8
11
|
import { MobileConfigService } from './services/ios/mobile-config/index.js';
|
|
9
12
|
import MobileImageMounterService from './services/ios/mobile-image-mounter/index.js';
|
|
@@ -118,6 +121,26 @@ export async function startWebInspectorService(udid) {
|
|
|
118
121
|
]),
|
|
119
122
|
};
|
|
120
123
|
}
|
|
124
|
+
export async function startDVTService(udid) {
|
|
125
|
+
const { remoteXPC, tunnelConnection } = await createRemoteXPCConnection(udid);
|
|
126
|
+
const dvtServiceDescriptor = remoteXPC.findService(DVTSecureSocketProxyService.RSD_SERVICE_NAME);
|
|
127
|
+
// Create DVT service instance
|
|
128
|
+
const dvtService = new DVTSecureSocketProxyService([
|
|
129
|
+
tunnelConnection.host,
|
|
130
|
+
parseInt(dvtServiceDescriptor.port, 10),
|
|
131
|
+
]);
|
|
132
|
+
// Connect to DVT service
|
|
133
|
+
await dvtService.connect();
|
|
134
|
+
// Create instrument services
|
|
135
|
+
const locationSimulation = new LocationSimulation(dvtService);
|
|
136
|
+
const conditionInducer = new ConditionInducer(dvtService);
|
|
137
|
+
return {
|
|
138
|
+
remoteXPC: remoteXPC,
|
|
139
|
+
dvtService,
|
|
140
|
+
locationSimulation,
|
|
141
|
+
conditionInducer,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
121
144
|
export async function createRemoteXPCConnection(udid) {
|
|
122
145
|
const tunnelConnection = await getTunnelInformation(udid);
|
|
123
146
|
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.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"main": "build/src/index.js",
|
|
5
5
|
"types": "build/src/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -39,6 +39,9 @@
|
|
|
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:location-simulation-dvt": "mocha test/integration/dvt_instruments/location-simulation-test.ts --exit --timeout 1m",
|
|
44
|
+
"test:condition-inducer-dvt": "mocha test/integration/dvt_instruments/condition-inducer-test.ts --exit --timeout 1m",
|
|
42
45
|
"test:tunnel-creation": "sudo tsx scripts/test-tunnel-creation.ts",
|
|
43
46
|
"test:tunnel-creation:lsof": "sudo tsx scripts/test-tunnel-creation.ts --keep-open"
|
|
44
47
|
},
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,11 @@ export type {
|
|
|
24
24
|
WebInspectorService,
|
|
25
25
|
MisagentService,
|
|
26
26
|
SyslogService,
|
|
27
|
+
DVTSecureSocketProxyService,
|
|
28
|
+
LocationSimulationService,
|
|
29
|
+
ConditionInducerService,
|
|
30
|
+
ConditionProfile,
|
|
31
|
+
ConditionGroup,
|
|
27
32
|
SocketInfo,
|
|
28
33
|
TunnelResult,
|
|
29
34
|
TunnelRegistry,
|
|
@@ -36,6 +41,7 @@ export type {
|
|
|
36
41
|
SpringboardServiceWithConnection,
|
|
37
42
|
WebInspectorServiceWithConnection,
|
|
38
43
|
MisagentServiceWithConnection,
|
|
44
|
+
DVTServiceWithConnection,
|
|
39
45
|
} from './lib/types.js';
|
|
40
46
|
export { PowerAssertionType } from './lib/types.js';
|
|
41
47
|
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);
|
package/src/lib/plist/index.ts
CHANGED