appium-ios-device 2.1.0 → 2.2.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/README.md +1 -0
- package/build/lib/instrument/headers.js +356 -0
- package/build/lib/instrument/index.js +187 -0
- package/build/lib/instrument/transformer/dtx-decode.js +108 -0
- package/build/lib/instrument/transformer/dtx-encode.js +57 -0
- package/build/lib/instrument/transformer/nskeyed.js +446 -0
- package/build/lib/services.js +12 -3
- package/build/lib/ssl-helper.js +39 -1
- package/build/lib/utilities.js +3 -3
- package/lib/instrument/headers.js +421 -0
- package/lib/instrument/index.js +190 -0
- package/lib/instrument/transformer/dtx-decode.js +70 -0
- package/lib/instrument/transformer/dtx-encode.js +33 -0
- package/lib/instrument/transformer/nskeyed.js +447 -0
- package/lib/services.js +14 -3
- package/lib/ssl-helper.js +43 -2
- package/lib/utilities.js +6 -3
- package/package.json +9 -3
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import plistlib from 'bplist-parser';
|
|
2
|
+
import bplistCreate from 'bplist-creator';
|
|
3
|
+
import {parse as uuidParse, stringify as uuidStringify} from 'uuid';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
|
|
6
|
+
const NSKEYED_ARCHIVE_VERSION = 100_000;
|
|
7
|
+
const NULL_UID = new plistlib.UID(0);
|
|
8
|
+
const CYCLE_TOKEN = 'CycleToken';
|
|
9
|
+
const PRIMITIVE_TYPES = ['Number', 'String', 'Boolean', 'UID', 'Buffer'];
|
|
10
|
+
const NON_ENCODABLE_TYPES = ['number', 'boolean'];
|
|
11
|
+
const NSKEYEDARCHIVER = 'NSKeyedArchiver';
|
|
12
|
+
const UNIX2APPLE_TIMESTAMP_SECOND = 978307200;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArchivedObject {
|
|
16
|
+
/**
|
|
17
|
+
* Stateful wrapper around Archive for an object being archived.
|
|
18
|
+
* @param {Object} object
|
|
19
|
+
* @param {Unarchive} unarchiver
|
|
20
|
+
* @constructor
|
|
21
|
+
*/
|
|
22
|
+
constructor (object, unarchiver) {
|
|
23
|
+
this.object = object;
|
|
24
|
+
this._unarchiver = unarchiver;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
decodeIndex (index) {
|
|
28
|
+
return this._unarchiver.decodeObject(index);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
decode (key) {
|
|
32
|
+
return this._unarchiver.decodeKey(this.object, key);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class ArchivingObject {
|
|
37
|
+
/**
|
|
38
|
+
* Stateful wrapper around Unarchive for an archived object
|
|
39
|
+
* @param {Object} object
|
|
40
|
+
* @param {Archive} archiver
|
|
41
|
+
* @constructor
|
|
42
|
+
*/
|
|
43
|
+
constructor (object, archiver) {
|
|
44
|
+
this._archiveObj = object;
|
|
45
|
+
this._archiver = archiver;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
encode (key, val) {
|
|
49
|
+
this._archiveObj[key] = this._archiver.encode(val);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* This class must be inherited when creating an archive/unarchive subclass
|
|
55
|
+
* And you need to call `updateNSKeyedArchiveClass` add subclass to archive/unarchive object
|
|
56
|
+
*/
|
|
57
|
+
class BaseArchiveHandler {
|
|
58
|
+
/**
|
|
59
|
+
* @param {ArchivedObject} archive
|
|
60
|
+
*/
|
|
61
|
+
// eslint-disable-next-line no-unused-vars
|
|
62
|
+
decodeArchive (archive) {
|
|
63
|
+
throw new Error(`Did not know how to decode the object`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {Object} obj an instance of this class
|
|
68
|
+
* @param {ArchivingObject} archive
|
|
69
|
+
*/
|
|
70
|
+
// eslint-disable-next-line no-unused-vars
|
|
71
|
+
encodeArchive (obj, archive) {
|
|
72
|
+
throw new Error(`Did not know how to encode the object`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* "Delegate for packing/unpacking NS(Mutable)Dictionary objects"
|
|
78
|
+
*/
|
|
79
|
+
class DictArchive extends BaseArchiveHandler {
|
|
80
|
+
decodeArchive (archive) {
|
|
81
|
+
const keyUids = archive.decode('NS.keys');
|
|
82
|
+
const valUids = archive.decode('NS.objects');
|
|
83
|
+
const d = {};
|
|
84
|
+
for (let i = 0; i < keyUids.length ; i++) {
|
|
85
|
+
const key = archive.decodeIndex(keyUids[i]);
|
|
86
|
+
d[key] = archive.decodeIndex(valUids[i]);
|
|
87
|
+
}
|
|
88
|
+
return d;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Delegate for packing/unpacking NS(Mutable)Array objects
|
|
94
|
+
*/
|
|
95
|
+
class ListArchive extends BaseArchiveHandler {
|
|
96
|
+
decodeArchive (archive) {
|
|
97
|
+
const uids = archive.decode('NS.objects');
|
|
98
|
+
return uids.map(archive.decodeIndex.bind(archive));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
class DTTapMessagePlist extends BaseArchiveHandler {
|
|
103
|
+
decodeArchive (archive) {
|
|
104
|
+
return archive.decode('DTTapMessagePlist');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class NSError extends BaseArchiveHandler {
|
|
109
|
+
decodeArchive (archive) {
|
|
110
|
+
return {
|
|
111
|
+
'$class': 'NSError',
|
|
112
|
+
'domain': archive.decode('NSDomain'),
|
|
113
|
+
'userinfo': archive.decode('NSUserInfo'),
|
|
114
|
+
'code': archive.decode('NSCode')
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
class NSException extends BaseArchiveHandler {
|
|
120
|
+
decodeArchive (archive) {
|
|
121
|
+
return {
|
|
122
|
+
'$class': 'NSException',
|
|
123
|
+
'reason': archive.decode('NS.reason'),
|
|
124
|
+
'userinfo': archive.decode('userinfo'),
|
|
125
|
+
'name': archive.decode('NS.name')
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
class NSURL extends BaseArchiveHandler {
|
|
131
|
+
/**
|
|
132
|
+
* @param {*} base
|
|
133
|
+
* @param {string} relative usually ios device relative path e.g: file://xx/
|
|
134
|
+
*/
|
|
135
|
+
constructor (base, relative) {
|
|
136
|
+
super();
|
|
137
|
+
this._base = base;
|
|
138
|
+
this._relative = relative;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
decodeArchive (archive) {
|
|
142
|
+
return {$class: 'NSURL', base: archive.decode('NS.base'), relative: archive.decode('NS.relative')};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
encodeArchive (obj, archive) {
|
|
146
|
+
archive.encode('NS.base', obj._base);
|
|
147
|
+
archive.encode('NS.relative', obj._relative);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
class NSDate extends BaseArchiveHandler {
|
|
152
|
+
/**
|
|
153
|
+
* @param {number} data timestamp in seconds
|
|
154
|
+
*/
|
|
155
|
+
constructor (data) {
|
|
156
|
+
super();
|
|
157
|
+
this._data = data;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
decodeArchive (archive) {
|
|
161
|
+
return UNIX2APPLE_TIMESTAMP_SECOND + archive.decode('NS.time');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
encodeArchive (obj, archive) {
|
|
165
|
+
archive.encode('NS.time', obj._data - UNIX2APPLE_TIMESTAMP_SECOND);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class NSMutableString extends BaseArchiveHandler {
|
|
170
|
+
decodeArchive (archive) {
|
|
171
|
+
return archive.decode('NS.string');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
class NSMutableData extends BaseArchiveHandler {
|
|
176
|
+
decodeArchive (archive) {
|
|
177
|
+
return archive.decode('NS.data');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
class NSUUID extends BaseArchiveHandler {
|
|
182
|
+
/**
|
|
183
|
+
* @param {string} data uuid format data e.g:00000000-0000-0000-0000-000000000000
|
|
184
|
+
*/
|
|
185
|
+
constructor (data) {
|
|
186
|
+
super();
|
|
187
|
+
this._data = data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
decodeArchive (archive) {
|
|
191
|
+
return uuidStringify(archive.decode('NS.uuidbytes'));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
encodeArchive (obj, archive) {
|
|
195
|
+
archive._archiveObj['NS.uuidbytes'] = Buffer.from(uuidParse(obj._data).buffer);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
class XCTCapabilities extends BaseArchiveHandler {
|
|
200
|
+
decodeArchive (archive) {
|
|
201
|
+
return archive.decode('capabilities-dictionary');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
class NSNull extends BaseArchiveHandler {
|
|
206
|
+
decodeArchive () {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* decode and encode Archive of currently known data formats
|
|
213
|
+
*/
|
|
214
|
+
const UNARCHIVE_CLASS_MAP = {
|
|
215
|
+
DTTapMessagePlist,
|
|
216
|
+
DTSysmonTapMessage: DTTapMessagePlist,
|
|
217
|
+
DTTapHeartbeatMessage: DTTapMessagePlist,
|
|
218
|
+
DTTapMessageArchive: DTTapMessagePlist,
|
|
219
|
+
DTKTraceTapMessage: DTTapMessagePlist,
|
|
220
|
+
ErrorArchive: NSError,
|
|
221
|
+
ExceptionArchive: NSException,
|
|
222
|
+
NSDictionary: DictArchive,
|
|
223
|
+
NSMutableDictionary: DictArchive,
|
|
224
|
+
NSArray: ListArchive,
|
|
225
|
+
NSMutableArray: ListArchive,
|
|
226
|
+
NSMutableSet: ListArchive,
|
|
227
|
+
NSSet: ListArchive,
|
|
228
|
+
NSDate,
|
|
229
|
+
NSError,
|
|
230
|
+
NSException,
|
|
231
|
+
NSMutableString,
|
|
232
|
+
NSMutableData,
|
|
233
|
+
NSNull,
|
|
234
|
+
NSUUID,
|
|
235
|
+
NSURL,
|
|
236
|
+
XCTCapabilities
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Capable of unpacking an archived object tree in the NSKeyedArchive format.
|
|
241
|
+
* Apple's implementation can be found here:
|
|
242
|
+
* https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/NSKeyedUnarchiver.swift
|
|
243
|
+
*/
|
|
244
|
+
class Unarchive {
|
|
245
|
+
constructor (inputBytes) {
|
|
246
|
+
this.input = inputBytes;
|
|
247
|
+
this.unpackedUids = {};
|
|
248
|
+
this.topUID = NULL_UID;
|
|
249
|
+
this.objects = [];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
unpackArchiveHeader () {
|
|
253
|
+
const plist = plistlib.parseBuffer(this.input)[0];
|
|
254
|
+
if (plist.$archiver !== NSKEYEDARCHIVER) {
|
|
255
|
+
throw new Error(`unsupported encoder: ${plist.$archiver}`);
|
|
256
|
+
}
|
|
257
|
+
if (plist.$version !== NSKEYED_ARCHIVE_VERSION) {
|
|
258
|
+
throw new Error(`expected ${NSKEYED_ARCHIVE_VERSION}, got ${plist.$version }`);
|
|
259
|
+
}
|
|
260
|
+
const top = plist.$top;
|
|
261
|
+
const topUID = top.root;
|
|
262
|
+
if (!topUID) {
|
|
263
|
+
throw new Error(`top object did not have a UID! dump: ${JSON.stringify(top)}`);
|
|
264
|
+
}
|
|
265
|
+
this.topUID = topUID;
|
|
266
|
+
this.objects = plist.$objects;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* use the UNARCHIVE_CLASS_MAP to find the unarchiving delegate of a uid
|
|
271
|
+
*/
|
|
272
|
+
classForUid (index) {
|
|
273
|
+
const meta = this.objects[index.UID];
|
|
274
|
+
const name = meta.$classname;
|
|
275
|
+
const klass = UNARCHIVE_CLASS_MAP[name];
|
|
276
|
+
if (!klass) {
|
|
277
|
+
throw new Error(`no mapping for ${name} in UNARCHIVE_CLASS_MAP`);
|
|
278
|
+
}
|
|
279
|
+
return klass;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
decodeKey (obj, key) {
|
|
283
|
+
const val = obj[key];
|
|
284
|
+
return _.isNil(val?.UID) ? val : this.decodeObject(val);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
decodeObject (index) {
|
|
288
|
+
if (index === NULL_UID) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
const obj = this.unpackedUids[index];
|
|
292
|
+
if (obj === CYCLE_TOKEN) {
|
|
293
|
+
throw new Error(`archive has a cycle with ${index}`);
|
|
294
|
+
}
|
|
295
|
+
if (!_.isUndefined(obj)) {
|
|
296
|
+
return obj;
|
|
297
|
+
}
|
|
298
|
+
const rawObj = this.objects[index.UID];
|
|
299
|
+
this.unpackedUids[index.UID] = CYCLE_TOKEN;
|
|
300
|
+
|
|
301
|
+
if (!rawObj?.$class) {
|
|
302
|
+
this.unpackedUids[index.UID] = obj;
|
|
303
|
+
return rawObj;
|
|
304
|
+
}
|
|
305
|
+
const klass = this.classForUid(rawObj.$class);
|
|
306
|
+
const klassObj = new klass().decodeArchive(new ArchivedObject(rawObj, this));
|
|
307
|
+
this.unpackedUids[index.UID] = klassObj;
|
|
308
|
+
return klassObj;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
toObject () {
|
|
312
|
+
this.unpackArchiveHeader();
|
|
313
|
+
return this.decodeObject(this.topUID);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Capable of packing an object tree into the NSKeyedArchive format.
|
|
319
|
+
* Apple's implementation can be found here:
|
|
320
|
+
* https://github.com/apple/swift-corelibs-foundation/blob/main/Sources/Foundation/NSKeyedArchiver.swift
|
|
321
|
+
*/
|
|
322
|
+
class Archive {
|
|
323
|
+
constructor (inputObject) {
|
|
324
|
+
this.input = inputObject;
|
|
325
|
+
this.classMap = {};
|
|
326
|
+
this.objects = ['$null']; // objects that go directly into the archive, always start with $null
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
uidForArchiver (archiver) {
|
|
330
|
+
let val = this.classMap[archiver];
|
|
331
|
+
if (val) {
|
|
332
|
+
return new plistlib.UID(val);
|
|
333
|
+
}
|
|
334
|
+
this.classMap[archiver] = this.objects.length;
|
|
335
|
+
val = new plistlib.UID(this.objects.length);
|
|
336
|
+
this.objects.push({
|
|
337
|
+
'$classes': [archiver],
|
|
338
|
+
'$classname': archiver
|
|
339
|
+
});
|
|
340
|
+
return val;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
archive (obj) {
|
|
344
|
+
if (_.isUndefined(obj) || _.isNull(obj)) {
|
|
345
|
+
return NULL_UID;
|
|
346
|
+
}
|
|
347
|
+
const index = new plistlib.UID(this.objects.length);
|
|
348
|
+
if (PRIMITIVE_TYPES.includes(obj.constructor.name)) {
|
|
349
|
+
this.objects.push(obj);
|
|
350
|
+
return index;
|
|
351
|
+
}
|
|
352
|
+
const archiveObj = {};
|
|
353
|
+
this.objects.push(archiveObj);
|
|
354
|
+
this.encodeTopLevel(obj, archiveObj);
|
|
355
|
+
return index;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
encode (val) {
|
|
359
|
+
if (NON_ENCODABLE_TYPES.includes(typeof val)) {
|
|
360
|
+
return val;
|
|
361
|
+
}
|
|
362
|
+
return this.archive(val);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
encodeTopLevel (obj, archiveObj) {
|
|
366
|
+
if (obj instanceof Array) {
|
|
367
|
+
return this.encodeArray(obj, archiveObj);
|
|
368
|
+
} else if (obj instanceof Set) {
|
|
369
|
+
return this.encodeSet(obj, archiveObj);
|
|
370
|
+
} else if (obj instanceof Object) {
|
|
371
|
+
const objName = obj.constructor.name;
|
|
372
|
+
// Only special class instance are useful, such as NSURL, NSUUID, NSDate.
|
|
373
|
+
// And this class must also have the encodeArchive method
|
|
374
|
+
if (objName in UNARCHIVE_CLASS_MAP) {
|
|
375
|
+
archiveObj.$class = this.uidForArchiver(objName);
|
|
376
|
+
obj.encodeArchive(obj, new ArchivingObject(archiveObj, this));
|
|
377
|
+
} else {
|
|
378
|
+
return this.encodeDict(obj, archiveObj);
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
throw Error(`Unable to encode types: ${typeof obj}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
encodeArray (objs, archiveObj) {
|
|
386
|
+
archiveObj.$class = this.uidForArchiver('NSArray');
|
|
387
|
+
archiveObj['NS.objects'] = objs.map(this.archive.bind(this));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
encodeSet (objs, archiveObj) {
|
|
391
|
+
archiveObj.$class = this.uidForArchiver('NSSet');
|
|
392
|
+
archiveObj['NS.objects'] = objs.map(this.archive.bind(this));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
encodeDict (obj, archiveObj) {
|
|
396
|
+
archiveObj.$class = this.uidForArchiver('NSDictionary');
|
|
397
|
+
archiveObj['NS.keys'] = _.keys(obj).map(this.archive.bind(this));
|
|
398
|
+
archiveObj['NS.objects'] = _.values(obj).map(this.archive.bind(this));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
toBytes () {
|
|
402
|
+
if (this.objects.length === 1) {
|
|
403
|
+
this.archive(this.input);
|
|
404
|
+
}
|
|
405
|
+
const d = {
|
|
406
|
+
'$version': NSKEYED_ARCHIVE_VERSION,
|
|
407
|
+
'$archiver': NSKEYEDARCHIVER,
|
|
408
|
+
'$top': {'root': new plistlib.UID(1)},
|
|
409
|
+
'$objects': this.objects
|
|
410
|
+
};
|
|
411
|
+
return bplistCreate(d);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Creates NSKeyed Buffer from an object
|
|
417
|
+
* @param {Object} inputObject
|
|
418
|
+
* @returns {Buffer} NSKeyed Buffer
|
|
419
|
+
*/
|
|
420
|
+
function archive (inputObject) {
|
|
421
|
+
return new Archive(inputObject).toBytes();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Parses NSKeyed Buffer into JS Object
|
|
426
|
+
* @param {Buffer} inputBytes NSKeyed Buffer
|
|
427
|
+
* @returns {Object} JS Object
|
|
428
|
+
*/
|
|
429
|
+
function unarchive (inputBytes) {
|
|
430
|
+
return new Unarchive(inputBytes).toObject();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Update unknown NSKeyedArchive types for packing/unpacking
|
|
435
|
+
* @param {String} name packing/unpacking key name
|
|
436
|
+
* @param {BaseArchiveHandler} subClass inherit from BaseArchiveHandler class
|
|
437
|
+
*/
|
|
438
|
+
function updateNSKeyedArchiveClass (name, subClass) {
|
|
439
|
+
if (!_.isFunction(subClass.prototype?.decodeArchive) && !_.isFunction(subClass.prototype?.encodeArchive)) {
|
|
440
|
+
throw Error('subClass must have decodeArchive or encodeArchive methods');
|
|
441
|
+
}
|
|
442
|
+
if (!(name in UNARCHIVE_CLASS_MAP)) {
|
|
443
|
+
UNARCHIVE_CLASS_MAP[name] = subClass;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export { updateNSKeyedArchiveClass, BaseArchiveHandler, NSURL, NSUUID, NSDate, unarchive, archive};
|
package/lib/services.js
CHANGED
|
@@ -6,10 +6,12 @@ import { InstallationProxyService, INSTALLATION_PROXY_SERVICE_NAME } from './ins
|
|
|
6
6
|
import { AfcService, AFC_SERVICE_NAME } from './afc';
|
|
7
7
|
import { NotificationProxyService, NOTIFICATION_PROXY_SERVICE_NAME } from './notification-proxy';
|
|
8
8
|
import { HouseArrestService, HOUSE_ARREST_SERVICE_NAME } from './house-arrest';
|
|
9
|
+
import { InstrumentService, INSTRUMENT_SERVICE_NAME_VERSION_14, INSTRUMENT_SERVICE_NAME} from './instrument';
|
|
9
10
|
import PlistService from './plist-service';
|
|
10
11
|
import semver from 'semver';
|
|
11
12
|
|
|
12
13
|
const CRASH_LOG_SERVICE_NAME = 'com.apple.crashreportcopymobile';
|
|
14
|
+
const INSTRUMENT_HANDSHAKE_VERSION = 14;
|
|
13
15
|
|
|
14
16
|
async function startSyslogService (udid, opts = {}) {
|
|
15
17
|
const socket = await startService(udid, SYSLOG_SERVICE_NAME, opts.socket);
|
|
@@ -80,12 +82,21 @@ async function startHouseArrestService (udid, opts = {}) {
|
|
|
80
82
|
return new HouseArrestService(socket);
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
async function
|
|
85
|
+
async function startInstrumentService (udid, opts = {}) {
|
|
86
|
+
const osVersion = opts.osVersion || await getOSVersion(udid, opts.socket);
|
|
87
|
+
return new InstrumentService(
|
|
88
|
+
parseInt(osVersion.split('.')[0], 10) < INSTRUMENT_HANDSHAKE_VERSION
|
|
89
|
+
? await startService(udid, INSTRUMENT_SERVICE_NAME, opts.socket, true)
|
|
90
|
+
: await startService(udid, INSTRUMENT_SERVICE_NAME_VERSION_14, opts.socket)
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function startService (udid, serviceName, socket, handshakeOnly = false) {
|
|
84
95
|
const lockdown = await startLockdownSession(udid, socket);
|
|
85
96
|
try {
|
|
86
97
|
const service = await lockdown.startService(serviceName);
|
|
87
98
|
if (service.EnableServiceSSL) {
|
|
88
|
-
return await connectPortSSL(udid, service.Port, socket);
|
|
99
|
+
return await connectPortSSL(udid, service.Port, socket, handshakeOnly);
|
|
89
100
|
} else {
|
|
90
101
|
return await connectPort(udid, service.Port, socket);
|
|
91
102
|
}
|
|
@@ -98,5 +109,5 @@ export {
|
|
|
98
109
|
startSyslogService, startWebInspectorService,
|
|
99
110
|
startInstallationProxyService, startSimulateLocationService,
|
|
100
111
|
startAfcService, startCrashLogService, startNotificationProxyService,
|
|
101
|
-
startHouseArrestService
|
|
112
|
+
startHouseArrestService, startInstrumentService
|
|
102
113
|
};
|
package/lib/ssl-helper.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import tls from 'tls';
|
|
2
|
-
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import B from 'bluebird';
|
|
3
4
|
|
|
4
5
|
const TLS_VERSION = 'TLSv1_method';
|
|
6
|
+
const HANDSHAKE_TIMEOUT_MS = 10000;
|
|
5
7
|
|
|
6
8
|
function upgradeToSSL (socket, key, cert) {
|
|
7
9
|
return new tls.TLSSocket(socket, {
|
|
@@ -13,4 +15,43 @@ function upgradeToSSL (socket, key, cert) {
|
|
|
13
15
|
})
|
|
14
16
|
});
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* After the ssl protocol is successfully handshake, close the ssl protocol channel and use text transmission
|
|
21
|
+
* @param socket
|
|
22
|
+
* @param key
|
|
23
|
+
* @param cert
|
|
24
|
+
* @returns {Promise<Socket>} Duplicate the input socket
|
|
25
|
+
*/
|
|
26
|
+
async function enableSSLHandshakeOnly (socket, key, cert) {
|
|
27
|
+
const sslSocket = tls.connect({
|
|
28
|
+
socket,
|
|
29
|
+
secureContext: tls.createSecureContext({
|
|
30
|
+
key,
|
|
31
|
+
cert,
|
|
32
|
+
secureProtocol: TLS_VERSION
|
|
33
|
+
}),
|
|
34
|
+
rejectUnauthorized: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// stop receiving data after successful handshake
|
|
38
|
+
await new B((resolve, reject) => {
|
|
39
|
+
const timeoutHandler = setTimeout(() => {
|
|
40
|
+
if (!sslSocket.destroyed) {
|
|
41
|
+
sslSocket.end();
|
|
42
|
+
}
|
|
43
|
+
return reject(new Error('ssl handshake error'));
|
|
44
|
+
}, HANDSHAKE_TIMEOUT_MS);
|
|
45
|
+
|
|
46
|
+
sslSocket.once('secureConnect', () => {
|
|
47
|
+
clearTimeout(timeoutHandler);
|
|
48
|
+
sslSocket._handle.readStop();
|
|
49
|
+
return resolve();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
// Duplicate the socket. Return a new socket object connected to the same system resource
|
|
53
|
+
return net.Socket({fd: socket._handle.fd});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
export { upgradeToSSL, enableSSLHandshakeOnly };
|
package/lib/utilities.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Usbmux, { getDefaultSocket } from './usbmux';
|
|
2
|
-
import { upgradeToSSL } from './ssl-helper';
|
|
2
|
+
import { upgradeToSSL, enableSSLHandshakeOnly } from './ssl-helper';
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import log from './logger';
|
|
5
5
|
|
|
@@ -183,9 +183,10 @@ async function startLockdownSession (udid, socket = null) {
|
|
|
183
183
|
* @param {string} udid Device UDID
|
|
184
184
|
* @param {number} port Port to connect
|
|
185
185
|
* @param {?net.Socket} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed
|
|
186
|
+
* @param {boolean} handshakeOnly only handshake and return the initial socket
|
|
186
187
|
* @returns {tls.TLSSocket|Object} The socket or the object returned in the callback if the callback function exists
|
|
187
188
|
*/
|
|
188
|
-
async function connectPortSSL (udid, port, socket = null) {
|
|
189
|
+
async function connectPortSSL (udid, port, socket = null, handshakeOnly = false) {
|
|
189
190
|
const usbmux = new Usbmux(socket || await getDefaultSocket());
|
|
190
191
|
try {
|
|
191
192
|
const device = await usbmux.findDevice(udid);
|
|
@@ -197,7 +198,9 @@ async function connectPortSSL (udid, port, socket = null) {
|
|
|
197
198
|
throw new Error(`Could not find a pair record for device '${udid}'. Please first pair with the device`);
|
|
198
199
|
}
|
|
199
200
|
const socket = await usbmux.connect(device.Properties.DeviceID, port, undefined);
|
|
200
|
-
return
|
|
201
|
+
return handshakeOnly ?
|
|
202
|
+
await enableSSLHandshakeOnly(socket, pairRecord.HostPrivateKey, pairRecord.HostCertificate) :
|
|
203
|
+
upgradeToSSL(socket, pairRecord.HostPrivateKey, pairRecord.HostCertificate);
|
|
201
204
|
} catch (e) {
|
|
202
205
|
usbmux.close();
|
|
203
206
|
throw e;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"keywords": [
|
|
5
5
|
"appium"
|
|
6
6
|
],
|
|
7
|
-
"version": "2.
|
|
7
|
+
"version": "2.2.0",
|
|
8
8
|
"author": "appium",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"repository": {
|
|
@@ -31,10 +31,14 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@appium/support": "^2.55.3",
|
|
33
33
|
"@babel/runtime": "^7.0.0",
|
|
34
|
+
"asyncbox": "^2.9.2",
|
|
34
35
|
"bluebird": "^3.1.1",
|
|
36
|
+
"bplist-creator": "^0.x",
|
|
37
|
+
"bplist-parser": "^0.x",
|
|
35
38
|
"lodash": "^4.17.15",
|
|
36
39
|
"semver": "^7.0.0",
|
|
37
|
-
"source-map-support": "^0.x"
|
|
40
|
+
"source-map-support": "^0.x",
|
|
41
|
+
"uuid": "^8.3.2"
|
|
38
42
|
},
|
|
39
43
|
"scripts": {
|
|
40
44
|
"build": "gulp transpile",
|
|
@@ -54,9 +58,11 @@
|
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@appium/gulp-plugins": "^6.0.0",
|
|
56
60
|
"@appium/eslint-config-appium": "^5.0.0",
|
|
61
|
+
"@semantic-release/git": "^10.0.1",
|
|
57
62
|
"chai": "^4.1.2",
|
|
58
63
|
"chai-as-promised": "^7.1.1",
|
|
59
64
|
"gulp": "^4.0.0",
|
|
60
|
-
"pre-commit": "^1.1.3"
|
|
65
|
+
"pre-commit": "^1.1.3",
|
|
66
|
+
"semantic-release": "^19.0.2"
|
|
61
67
|
}
|
|
62
68
|
}
|