diff --git a/.eslintrc.json b/.eslintrc.json index 69a602c..950cea2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,22 @@ { - "extends": "@appium/eslint-config-appium", + "extends": ["@appium/eslint-config-appium-ts"], "overrides": [ { "files": "test/**/*.js", "rules": { - "func-names": "off" + "func-names": "off", + "@typescript-eslint/no-var-requires": "off" + } + }, + { + "files": "scripts/**/*", + "parserOptions": {"sourceType": "script"}, + "rules": { + "@typescript-eslint/no-var-requires": "off" } } - ] + ], + "rules": { + "require-await": "error" + } } diff --git a/.mocharc.js b/.mocharc.js index 66d4a97..40599e9 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -1,4 +1,4 @@ module.exports = { - require: ['@babel/register'], + require: ['ts-node/register'], forbidOnly: Boolean(process.env.CI) }; diff --git a/babel.config.json b/babel.config.json deleted file mode 100644 index 048e9cf..0000000 --- a/babel.config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "node": "14" - }, - "shippedProposals": true - } - ] - ], - "plugins": [ - "source-map-support", - "@babel/plugin-transform-runtime" - ], - "comments": false, - "sourceMaps": "both", - "env": { - "test": { - "retainLines": true, - "comments": true - } - } -} diff --git a/lib/afc/index.js b/lib/afc/index.js index 2513191..e09ff6e 100644 --- a/lib/afc/index.js +++ b/lib/afc/index.js @@ -84,7 +84,7 @@ class AfcService extends BaseServiceSocket { /** * Lists a directory's contents and returns them in an array * @param {string} path The path in unix format - * @return {Array.} + * @return {Promise} */ async listDirectory (path) { const {packetNumber, response} = this._createPacketPromise(`List directory '${path}'`); @@ -107,7 +107,7 @@ class AfcService extends BaseServiceSocket { * Opens a file and creates a file handle * @param {string} path The path in unix format * @param {number} mode The file mode that will be used - * @return {number} + * @return {Promise} */ async openFile (path, mode) { const {packetNumber, response} = this._createPacketPromise(`Open file '${path}'`); @@ -134,7 +134,7 @@ class AfcService extends BaseServiceSocket { * Opens a file and creates a nodejs write stream * @param {string} filePath The path in unix format * @param {Object} opts The regular options that are passed to a Stream.Writable - * @return {AfcWritableFileStream} + * @return {Promise} */ async createWriteStream (filePath, opts) { const fileHandle = await this.openFile(filePath, FileModes.w); @@ -144,8 +144,8 @@ class AfcService extends BaseServiceSocket { /** * Opens a file and creates a nodejs read stream * @param {string} filePath The path in unix format - * @param {Object} opts The regular options that are passed to a Stream.Readable - * @return {AfcReadableFileStream} + * @param {Object} options The regular options that are passed to a Stream.Readable + * @return {Promise} */ async createReadStream (filePath, options) { const fileHandle = await this.openFile(filePath, FileModes.r); @@ -198,7 +198,7 @@ class AfcService extends BaseServiceSocket { * Read a certain length of buffer from the file handle * @param {number} fileHandle The file handle to be used * @param {number} length The length that wants to be read from the file handle - * @return {Buffer} + * @return {Promise} */ async readFile (fileHandle, length) { const {packetNumber, response} = this._createPacketPromise(`Read from file handle '${fileHandle}'`); @@ -223,7 +223,7 @@ class AfcService extends BaseServiceSocket { /** * Get the file info of the given path * @param {string} path The path in unix format - * @return {FileInfo} + * @return {Promise} */ async getFileInfo (path) { const {packetNumber, response} = this._createPacketPromise(`Get file info '${path}'`); @@ -238,6 +238,7 @@ class AfcService extends BaseServiceSocket { if (res.opCode !== Operations.DATA) { this._checkStatus(res); } + // @ts-ignore this should be ok return new FileInfo(this._parseObject(res.content)); } diff --git a/lib/house-arrest/index.js b/lib/house-arrest/index.js index 934a330..955b7f4 100644 --- a/lib/house-arrest/index.js +++ b/lib/house-arrest/index.js @@ -34,7 +34,7 @@ class HouseArrestService extends BaseServiceSocket { * Vends into the application container and returns an AfcService * @param {string} bundleId The bundle id of the app container that we will enter to * @throws Will throw an error if house arrest fails to access the application's container - * @returns {AfcService} + * @returns {Promise} */ async vendContainer (bundleId) { const responsePromise = this._receivePlistPromise(); @@ -55,7 +55,7 @@ class HouseArrestService extends BaseServiceSocket { * Vends into the application documents and returns an AfcService * @param {string} bundleId The bundle id of the app documents that we will enter to * @throws Will throw an error if house arrest fails to access the application's documents - * @returns {AfcService} + * @returns {Promise} */ async vendDocuments (bundleId) { const responsePromise = this._receivePlistPromise(); diff --git a/lib/imagemounter/utils/list_developer_image.js b/lib/imagemounter/utils/list_developer_image.js index 7934e80..c6c3b72 100644 --- a/lib/imagemounter/utils/list_developer_image.js +++ b/lib/imagemounter/utils/list_developer_image.js @@ -162,7 +162,7 @@ async function searchAndDownloadDeveloperImageFromGithub(finalVersion, fullDownl let fileUrl; const fileList = await listGithubImageList(githubImageOption); if (!fileList) { - throw new Error(`Failed to list https://github.com/${githubRepo}`);; + throw new Error(`Failed to list https://github.com/${githubRepo}`); } const targetFile = _.find(fileList, (item) => fileNameRegExp.test(item.path)); const splitter = subFolderList.length > 0 ? '/' : ''; diff --git a/lib/installation-proxy/index.js b/lib/installation-proxy/index.js index 8ad5a0c..33d2da8 100644 --- a/lib/installation-proxy/index.js +++ b/lib/installation-proxy/index.js @@ -54,8 +54,8 @@ class InstallationProxyService extends BaseServicePlist { /** * Lists applications according to the opts and returns them as a map - * @param {ListApplicationOptions} opts the listing options that wants to be passed - * @returns A map of the applications which the key is the bundleId + * @param {Partial} opts the listing options that wants to be passed + * @returns {Promise>} A map of the applications which the key is the bundleId */ async listApplications (opts = {}) { const request = { @@ -93,8 +93,8 @@ class InstallationProxyService extends BaseServicePlist { /** * Lists applications according to the opts and returns them as a map - * @param {LookupApplicationOptions} opts the lookup options that wants to be passed - * @returns A map of the applications which the key is the bundleId + * @param {Partial} opts the lookup options that wants to be passed + * @returns {Promise>} A map of the applications which the key is the bundleId */ async lookupApplications (opts = {}) { const request = { @@ -146,6 +146,7 @@ class InstallationProxyService extends BaseServicePlist { return messages; } } + return messages; } _isFinished (response) { diff --git a/lib/instrument/headers.js b/lib/instrument/headers.js index e509d14..9de67fd 100644 --- a/lib/instrument/headers.js +++ b/lib/instrument/headers.js @@ -41,6 +41,7 @@ const AUX_TYPES = Object.freeze({ /** * @typedef {Object} DTXMessageOptions + * @property {any} selector * @property {number} identifier packet transmission sequence, this value is incremented for each request * @property {number} channelCode the data transmission of the service will use this channel code. * Each service has a separate channel @@ -52,7 +53,7 @@ const AUX_TYPES = Object.freeze({ class DTXMessageHeader { /** - * @param {DTXMessageOptions} data + * @param {Partial} data * @returns {Buffer} DTXMessageHeaderBuffer */ static build (data) { @@ -61,11 +62,11 @@ class DTXMessageHeader { messageHeader.writeUInt32LE(DTX_MESSAGE_HEADER_LENGTH, 4); messageHeader.writeUInt16LE(0, 8); messageHeader.writeUInt16LE(1, 10); - messageHeader.writeUInt32LE(data.payloadLength, 12); - messageHeader.writeUInt32LE(data.identifier, 16); - messageHeader.writeUInt32LE(data.conversationIndex, 20); - messageHeader.writeUInt32LE(data.channelCode, 24); - messageHeader.writeUInt32LE(data.expectsReply, 28); + messageHeader.writeUInt32LE(data.payloadLength ?? 0, 12); + messageHeader.writeUInt32LE(data.identifier ?? 0, 16); + messageHeader.writeUInt32LE(data.conversationIndex ?? 0, 20); + messageHeader.writeUInt32LE(data.channelCode ?? 0, 24); + messageHeader.writeUInt32LE(+(data.expectsReply ?? false), 28); return messageHeader; } @@ -166,7 +167,7 @@ class DTXMessageAux { * Parses nskeyed Buffer into js array * @param {Buffer} headerBuffer * @param {DTXMessagePayloadHeaderObject} payloadHeader - * @returns {Array} + * @returns {any[]} */ static parse (headerBuffer, payloadHeader) { let cursor = 0; @@ -298,7 +299,7 @@ class DTXMessageAuxBuffer { class DTXMessage { /** - * @param {DTXMessageOptions} opts + * @param {Partial} opts */ constructor (opts = {}) { const { @@ -415,6 +416,7 @@ class DTXMessage { return ret; } if (ret._payloadHeader.auxLength > 0) { + // @ts-ignore Not 100% sure if this ok ret.auxiliaries = DTXMessageAux.parse(payloadBuf.slice(cursor, cursor + ret._payloadHeader.auxLength), ret._payloadHeader); cursor += ret._payloadHeader.auxLength; } diff --git a/lib/instrument/index.js b/lib/instrument/index.js index 3d087ab..a76d402 100644 --- a/lib/instrument/index.js +++ b/lib/instrument/index.js @@ -48,7 +48,7 @@ class InstrumentService extends BaseServiceSocket { * @param {import('net').Socket} socketClient DTXMessage.selector * @param {DTXCallback?} event if empty will ignore any messages */ - constructor(socketClient, event) { + constructor(socketClient, event = null) { super(socketClient); this._undefinedCallback = event; this._callbacks = new events.EventEmitter(); @@ -172,8 +172,8 @@ class InstrumentService extends BaseServiceSocket { } if (data.conversationIndex === 1) { this._replyQueues[data.identifier].push(data); - } else if (this._channelCallbacks.listenerCount(CHANNEL_OFFSET - data.channelCode) > 0) { - this._channelCallbacks.emit(CHANNEL_OFFSET - data.channelCode, data); + } else if (this._channelCallbacks.listenerCount(`${CHANNEL_OFFSET - data.channelCode}`) > 0) { + this._channelCallbacks.emit(`${CHANNEL_OFFSET - data.channelCode}`, data); } else { const selector = data.selector; if (_.isString(selector) && this._callbacks.listenerCount(selector) > 0) { diff --git a/lib/instrument/transformer/dtx-decode.js b/lib/instrument/transformer/dtx-decode.js index 9fac71d..13c5955 100644 --- a/lib/instrument/transformer/dtx-decode.js +++ b/lib/instrument/transformer/dtx-decode.js @@ -32,7 +32,7 @@ class DTXDecoder extends Stream.Transform { if (this.header.fragmentId === 0) { // only the 0th fragment contains a message header if (!(this.header.channel in this._dtxManager)) { - this._dtxManager[this.header.channel] = {headerBuffer, payloadBuffer: new Buffer.allocUnsafe(0)}; + this._dtxManager[this.header.channel] = {headerBuffer, payloadBuffer: Buffer.allocUnsafe(0)}; } if (this.header.fragmentCount > 1) { // Continue to get the next message fragments diff --git a/lib/instrument/transformer/dtx-encode.js b/lib/instrument/transformer/dtx-encode.js index 60390a0..68b7059 100644 --- a/lib/instrument/transformer/dtx-encode.js +++ b/lib/instrument/transformer/dtx-encode.js @@ -9,7 +9,7 @@ class DTXEncoder extends Stream.Transform { } _transform (data, encoding, onData) { - this.push(this._encode(data), 'buffer'); + this.push(this._encode(data), 'binary'); onData(); } diff --git a/lib/instrument/transformer/nskeyed.js b/lib/instrument/transformer/nskeyed.js index 88df593..f5107b2 100644 --- a/lib/instrument/transformer/nskeyed.js +++ b/lib/instrument/transformer/nskeyed.js @@ -5,6 +5,7 @@ import _ from 'lodash'; import { format as stringFormat } from 'node:util'; const NSKEYED_ARCHIVE_VERSION = 100_000; +// @ts-ignore UID is not exposed to typedefs const NULL_UID = new plistlib.UID(0); const CYCLE_TOKEN = 'CycleToken'; const PRIMITIVE_TYPES = ['Number', 'String', 'Boolean', 'UID', 'Buffer']; @@ -65,16 +66,16 @@ class BaseArchiveHandler { /** * @param {ArchivedObject} archive */ - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars decodeArchive(archive) { throw new Error(`Did not know how to decode the object`); } /** - * @param {Object} obj an instance of this class + * @param {Object} obj an instance of this §class * @param {ArchivingObject} archive */ - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line @typescript-eslint/no-unused-vars encodeArchive(obj, archive) { throw new Error(`Did not know how to encode the object`); } @@ -276,7 +277,7 @@ class XCTestConfiguration extends BaseArchiveHandler { testsToSkip: undefined, treatMissingBaselinesAsFailures: false, userAttachmentLifetime: 1 - } + }; /** * @param {XCTestConfigurationPlist} data @@ -293,13 +294,19 @@ class XCTestConfiguration extends BaseArchiveHandler { data.testBundleURL = new NSURL(undefined, data.testBundleURL); } if (!(data.testBundleURL instanceof NSURL)) { - throw new TypeError(`Expected testBundleURL to be a valid NSURL instance, got ${data.testBundleURL.constructor.name} instead`); + throw new TypeError( + // @ts-ignore contructor is always present + `Expected testBundleURL to be a valid NSURL instance, got ${data.testBundleURL.constructor.name} instead` + ); } if (typeof data.sessionIdentifier === 'string') { data.sessionIdentifier = new NSUUID(data.sessionIdentifier); } if (!(data.sessionIdentifier instanceof NSUUID)) { - throw new TypeError(`Expected sessionIdentifier to be a valid NSUUID instance, got ${data.sessionIdentifier.constructor.name} instead`); + throw new TypeError( + // @ts-ignore contructor is always present + `Expected sessionIdentifier to be a valid NSUUID instance, got ${data.sessionIdentifier.constructor.name} instead` + ); } this._data = { ...XCTestConfiguration._default, ...data }; } @@ -432,10 +439,12 @@ class Unarchive { class Archive { constructor(inputObject) { this.input = inputObject; + /** @type {any[]} */ this.objects = ['$null']; // objects that go directly into the archive, always start with $null } uidForArchiver(archiver, ...addition) { + // @ts-ignore UID is not exposed to typedefs const val = new plistlib.UID(this.objects.length); this.objects.push({ $classes: [archiver, ...addition], @@ -448,6 +457,7 @@ class Archive { if (_.isUndefined(obj) || _.isNull(obj)) { return NULL_UID; } + // @ts-ignore UID is not exposed to typedefs const index = new plistlib.UID(this.objects.length); if (PRIMITIVE_TYPES.includes(obj.constructor.name)) { this.objects.push(obj); @@ -522,6 +532,7 @@ class Archive { const d = { $version: NSKEYED_ARCHIVE_VERSION, $archiver: NSKEYEDARCHIVER, + // @ts-ignore UID is not exposed to typedefs $top: { root: new plistlib.UID(1) }, $objects: this.objects }; @@ -553,6 +564,7 @@ function unarchive(inputBytes) { * @param {BaseArchiveHandler} subClass inherit from BaseArchiveHandler class */ function updateNSKeyedArchiveClass(name, subClass) { + // @ts-ignore prototype always exists if (!_.isFunction(subClass.prototype?.decodeArchive) && !_.isFunction(subClass.prototype?.encodeArchive)) { throw new Error('subClass must have decodeArchive or encodeArchive methods'); } diff --git a/lib/lockdown/index.js b/lib/lockdown/index.js index 6b1214b..c688f4b 100644 --- a/lib/lockdown/index.js +++ b/lib/lockdown/index.js @@ -10,7 +10,7 @@ class Lockdown extends BaseServicePlist { /** * Makes a query type request to lockdown * @param {number} [timeout=5000] the timeout of receiving a response from lockdownd - * @returns {Object} + * @returns {Promise} */ async queryType (timeout = 5000) { const data = await this._plistService.sendPlistAndReceive({ @@ -30,7 +30,7 @@ class Lockdown extends BaseServicePlist { * @param {string} hostID the host id which can be retrieved from the pair record * @param {string} systemBUID the host BUID which can be retrieved from the pair record * @param {number} [timeout=5000] the timeout of receiving a response from lockdownd - * @returns {Object} + * @returns {Promise} */ async startSession (hostID, systemBUID, timeout = 5000) { const data = await this._plistService.sendPlistAndReceive({ @@ -60,16 +60,16 @@ class Lockdown extends BaseServicePlist { /** * @typedef {Object} Query * - * @property {string?} key The key we want to access - * @property {string?} domain The domain where we want to access + * @property {string} Key The key we want to access + * @property {string} Domain The domain where we want to access */ /** * Gets values from the device according to the query passed * - * @param {Query} query the query we want to send to lockdownd + * @param {Partial} query the query we want to send to lockdownd * @param {number} [timeout=5000] the timeout of receiving a response from lockdownd - * @returns {*} The actual response value. It should never be `null` or `undefined` + * @returns {Promise} The actual response value. It should never be `null` or `undefined` * @throws {Error} If an unexpected response is received from lockdownd */ async getValue (query = {}, timeout = 5000) { @@ -90,7 +90,7 @@ class Lockdown extends BaseServicePlist { * Starts a service on the phone corresponding to the name * @param {string} serviceName the name of the service which we want to start * @param {number} [timeout=5000] the timeout of receiving a response from lockdownd - * @returns {Object} + * @returns {Promise} */ async startService (serviceName, timeout = 5000) { const data = await this._plistService.sendPlistAndReceive({ diff --git a/lib/mcinstall/index.js b/lib/mcinstall/index.js index 0200c38..bd89f01 100644 --- a/lib/mcinstall/index.js +++ b/lib/mcinstall/index.js @@ -19,7 +19,7 @@ class MCInstallProxyService extends BaseServicePlist { /** * @typedef {Object} ProfileList - * @property {Array} OrderedIdentifiers list of all profile ident + * @property {any[]} OrderedIdentifiers list of all profile ident * @property {Object} ProfileManifest * @property {Object} ProfileMetadata * @property {String} Status @@ -27,7 +27,7 @@ class MCInstallProxyService extends BaseServicePlist { /** * Get all profiles of iOS devices - * @returns {ProfileList} + * @returns {Promise} * e.g. * { * OrderedIdentifiers: [ '2fac1c2b3d684843189b2981c718b0132854a847a' ], @@ -57,7 +57,7 @@ class MCInstallProxyService extends BaseServicePlist { * Install profile to iOS device * @param {String} path must be a certificate file .PEM .CER and more formats * e.g: /Downloads/charles-certificate.pem - * @returns {Object} e.g. {Status: 'Acknowledged'} + * @returns {Promise>} e.g. {Status: 'Acknowledged'} */ async installProfile (path) { const payload = await fs.readFile(path); @@ -68,7 +68,7 @@ class MCInstallProxyService extends BaseServicePlist { /** * Remove profile from iOS device * @param {String} ident Query identifier list through getProfileList method - * @returns {Object} e.g. {Status: 'Acknowledged'} + * @returns {Promise>} e.g. {Status: 'Acknowledged'} */ async removeProfile (ident) { const profiles = await this.getProfileList(); diff --git a/lib/notification-proxy/index.js b/lib/notification-proxy/index.js index 595ebcc..725c493 100644 --- a/lib/notification-proxy/index.js +++ b/lib/notification-proxy/index.js @@ -92,7 +92,6 @@ class NotificationProxyService extends BaseServiceSocket { /** * The api to shutdown the proxy. Consequently, all the notifications that are observing will recieve the proxyDeath response - * @param {*} notification The name of the notification which is desired notified */ shutdown () { this._encoder.write({ diff --git a/lib/plist-service/transformer/plist-service-encoder.js b/lib/plist-service/transformer/plist-service-encoder.js index 4e6fbd0..894b4c5 100644 --- a/lib/plist-service/transformer/plist-service-encoder.js +++ b/lib/plist-service/transformer/plist-service-encoder.js @@ -10,7 +10,7 @@ class PlistServiceEncoder extends Stream.Transform { } _transform (data, encoding, onData) { - this.push(this._encode(data), 'buffer'); + this.push(this._encode(data), 'binary'); onData(); } diff --git a/lib/ssl-helper.js b/lib/ssl-helper.js index f316a0f..0c67274 100644 --- a/lib/ssl-helper.js +++ b/lib/ssl-helper.js @@ -19,7 +19,7 @@ function upgradeToSSL (socket, key, cert) { * @param socket * @param key * @param cert - * @returns {Promise} Duplicate the input socket + * @returns {Promise} Duplicate the input socket */ async function enableSSLHandshakeOnly (socket, key, cert) { const sslSocket = tls.connect({ @@ -42,12 +42,13 @@ async function enableSSLHandshakeOnly (socket, key, cert) { sslSocket.once('secureConnect', () => { clearTimeout(timeoutHandler); + // @ts-ignore This is a hack sslSocket._handle.readStop(); return resolve(); }); }); // Duplicate the socket. Return a new socket object connected to the same system resource - return net.Socket({fd: socket._handle.fd}); + return new net.Socket({fd: socket._handle.fd}); } diff --git a/lib/usbmux/index.js b/lib/usbmux/index.js index a501709..77e4041 100644 --- a/lib/usbmux/index.js +++ b/lib/usbmux/index.js @@ -55,11 +55,11 @@ function swap16 (val) { /** * Connects a socket to usbmuxd service * - * @param {SocketOptions?} opts + * @param {Partial} opts * @throws {Error} If there was an error while accessing the socket or * a connection error happened * @throws {B.TimeoutError} if connection timeout happened - * @returns {import('net').Socket} Connected socket instance + * @returns {Promise} Connected socket instance */ async function getDefaultSocket (opts = {}) { const { @@ -70,11 +70,12 @@ async function getDefaultSocket (opts = {}) { } = opts; let socket; - if (await fs.exists(socketPath)) { - socket = net.createConnection(socketPath); + if (await fs.exists(socketPath ?? '')) { + socket = net.createConnection(socketPath ?? ''); } else if (process.platform === 'win32' || (process.platform === 'linux' && /microsoft/i.test(os.release()))) { // Connect to usbmuxd when running on WSL1 + // @ts-ignore socketPort should be what we need socket = net.createConnection(socketPort, socketHost); } else { throw new Error(`The usbmuxd socket at '${socketPath}' does not exist or is not accessible`); @@ -83,7 +84,7 @@ async function getDefaultSocket (opts = {}) { return await new B((resolve, reject) => { socket.once('error', reject); socket.once('connect', () => resolve(socket)); - }).timeout(timeout); + }).timeout(timeout ?? 5000); } @@ -119,7 +120,7 @@ class Usbmux extends BaseServiceSocket { /** * Returns the BUID of the host computer from usbmuxd * @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd - * @returns {string} + * @returns {Promise} */ async readBUID (timeout = 5000) { const {tag, receivePromise} = this._receivePlistPromise(timeout, (data) => { @@ -145,7 +146,7 @@ class Usbmux extends BaseServiceSocket { * Reads the pair record of a device. It will return null if it doesn't exists * @param {string} udid the udid of the device * @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd - * @returns {Object?} + * @returns {Promise} */ async readPairRecord (udid, timeout = 5000) { const {tag, receivePromise} = this._receivePlistPromise(timeout, (data) => { @@ -193,7 +194,7 @@ class Usbmux extends BaseServiceSocket { /** * Lists all devices connected to the host * @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd - * @returns {Array} + * @returns {Promise} */ async listDevices (timeout = 5000) { const {tag, receivePromise} = this._receivePlistPromise(timeout, (data) => { @@ -219,7 +220,7 @@ class Usbmux extends BaseServiceSocket { * Looks for a device with the passed udid. It will return undefined if the device is not found * @param {string} udid the udid of the device * @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd - * @returns {Object?} + * @returns {Promise} */ async findDevice (udid, timeout = 5000) { const devices = await this.listDevices(timeout); @@ -230,7 +231,7 @@ class Usbmux extends BaseServiceSocket { * Connects to the lockdownd on the device and returns a Lockdown client * @param {string} udid the udid of the device * @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd - * @returns {Lockdown} + * @returns {Promise} */ async connectLockdown (udid, timeout = 5000) { const device = await this.findDevice(udid, timeout); @@ -246,7 +247,7 @@ class Usbmux extends BaseServiceSocket { * @param {string} deviceID the device id which can be retrieved from the properties of a device * @param {number} port the port number that wants to be connected * @param {number} [timeout=5000] the timeout of receiving a response from usbmuxd - * @returns {net.Socket|Object} The socket or the object returned in the callback if the callback function exists + * @returns {Promise} The socket or the object returned in the callback if the callback function exists */ async connect (deviceID, port, timeout = 5000) { const {tag, receivePromise} = this._receivePlistPromise(timeout, (data) => { diff --git a/lib/utilities.js b/lib/utilities.js index 33204d4..766b297 100644 --- a/lib/utilities.js +++ b/lib/utilities.js @@ -18,7 +18,7 @@ const LOCKDOWN_REQUEST = { * Retrieves the udids of the connected devices * * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {Array.} The list of device serial numbers (udid) or + * @returns {Promise} The list of device serial numbers (udid) or * an empty list if no devices are connected */ async function getConnectedDevices(socket = null) { @@ -43,7 +43,7 @@ async function getConnectedDevices(socket = null) { * * @param {string} udid Device UDID * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {string} + * @returns {Promise} */ async function getOSVersion(udid, socket = null) { const usbmux = new Usbmux(socket || await getDefaultSocket()); @@ -61,7 +61,7 @@ async function getOSVersion(udid, socket = null) { * * @param {string} udid Device UDID * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {string} + * @returns {Promise} */ async function getDeviceName(udid, socket = null) { const usbmux = new Usbmux(socket || await getDefaultSocket()); @@ -78,7 +78,7 @@ async function getDeviceName(udid, socket = null) { * Returns all available device values * @param {string} udid Device UDID * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {object} Returns available default device values via lockdown. + * @returns {Promise} Returns available default device values via lockdown. * e.g. * { * "BasebandCertId"=>3840149528, @@ -137,7 +137,7 @@ async function getDeviceInfo(udid, socket = null) { * * @param {string} udid Device UDID * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {DeviceTime} + * @returns {Promise} */ async function getDeviceTime(udid, socket = null) { const lockdown = await startLockdownSession(udid, socket); @@ -158,7 +158,7 @@ async function getDeviceTime(udid, socket = null) { * * @param {string} udid Device UDID * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {Lockdown} + * @returns {Promise} */ async function startLockdownSession(udid, socket = null) { const usbmux = new Usbmux(socket || await getDefaultSocket()); @@ -185,7 +185,7 @@ async function startLockdownSession(udid, socket = null) { * @param {number} port Port to connect * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed * @param {boolean} handshakeOnly only handshake and return the initial socket - * @returns {tls.TLSSocket|Object} The socket or the object returned in the callback if the callback function exists + * @returns {Promise} The socket or the object returned in the callback if the callback function exists */ async function connectPortSSL(udid, port, socket = null, handshakeOnly = false) { const usbmux = new Usbmux(socket || await getDefaultSocket()); @@ -214,7 +214,7 @@ async function connectPortSSL(udid, port, socket = null, handshakeOnly = false) * @param {string} udid Device UDID * @param {number} port Port to connect * @param {import('net').Socket?} socket the socket of usbmuxd. It will default to /var/run/usbmuxd if it is not passed - * @returns {net.Socket|Object} The socket or the object returned in the callback if the callback function exists + * @returns {Promise} The socket or the object returned in the callback if the callback function exists */ async function connectPort(udid, port, socket = null) { const usbmux = new Usbmux(socket || await getDefaultSocket()); diff --git a/lib/webinspector/index.js b/lib/webinspector/index.js index 20fd6c3..9030676 100644 --- a/lib/webinspector/index.js +++ b/lib/webinspector/index.js @@ -53,10 +53,13 @@ function cleanupRpcObject (obj) { */ class WebInspectorService extends BaseServiceSocket { + /** @type {number|undefined} */ + _majorOsVersion; + /** * The main service for communication with the webinspectord * - * @param {WebInspectorServiceOptions} + * @param {Partial} opts */ constructor (opts = {}) { const { @@ -85,7 +88,7 @@ class WebInspectorService extends BaseServiceSocket { this._isSimulator = isSimulator; this._majorOsVersion = majorOsVersion; - if (!isSimulator && majorOsVersion < PARTIAL_MESSAGE_SUPPORT_DEPRECATION_VERSION) { + if (!isSimulator && !_.isNil(majorOsVersion) && majorOsVersion < PARTIAL_MESSAGE_SUPPORT_DEPRECATION_VERSION) { this._initializePartialMessageSupport(verboseHexDump, maxFrameLength); } else { this._initializeFullMessageSupport(verboseHexDump, maxFrameLength); @@ -166,11 +169,13 @@ class WebInspectorService extends BaseServiceSocket { let lastError; try { try { + // @ts-ignore _encoder must be present this._encoder.write(message); } catch (e) { // Workaround for https://github.com/joeferner/node-bplist-creator/issues/29 if (e instanceof TypeError) { message = cleanupRpcObject(message); + // @ts-ignore _encoder must be present this._encoder.write(message); } else { throw e; @@ -182,10 +187,10 @@ class WebInspectorService extends BaseServiceSocket { if (this._verbose || lastError) { log.debug('Sent message to Web Inspector:'); - log.debug(util.jsonStringify(message)); + log.debug(util.jsonStringify(message, null)); if (!_.isEqual(message, rpcObject)) { log.debug('Original message:'); - log.debug(util.jsonStringify(rpcObject)); + log.debug(util.jsonStringify(rpcObject, null)); } } @@ -197,7 +202,9 @@ class WebInspectorService extends BaseServiceSocket { // gets sent to the device. without this it will periodically hang with // nothing sent // however, this causes webinspectord to crash on devices running iOS 10 + // @ts-ignore _majorOsVersion could be present if (!this._isSimulator && this._majorOsVersion >= PARTIAL_MESSAGE_SUPPORT_DEPRECATION_VERSION) { + // @ts-ignore _encoder must be present this._encoder.write(' '); } } @@ -205,17 +212,18 @@ class WebInspectorService extends BaseServiceSocket { /** The callback function which will be called during message listening * @callback MessageCallback * @param {Object} object The rpc object that is sent from the webinspectord - */ + */ /** * Listen to messages coming from webinspectord - * @param {MessageCallback} callback + * @param {MessageCallback} onData */ listenMessage (onData) { + // @ts-ignore _decoder must be present this._decoder.on('data', (data) => { if (this._verbose) { log.debug('Received message from Web Inspector:'); - log.debug(util.jsonStringify(data)); + log.debug(util.jsonStringify(data, null)); } onData(data); }); diff --git a/lib/xctest.js b/lib/xctest.js index 81d63a1..315a055 100644 --- a/lib/xctest.js +++ b/lib/xctest.js @@ -38,12 +38,17 @@ const MAGIC_CHANNEL = 0xFFFFFFFF; * This class simulates the procedure which xcode uses to invoke xctests. */ class Xctest { + /** @type {import('./testmanagerd').TestmanagerdService|undefined} */ + _initialControlSession; + + /** @type {import('./testmanagerd').TestmanagerdService|undefined} */ + _execTestPlanSession; /** * @param {string} udid Device udid. * @param {string} xctestBundleId Bundle Id of xctest app on device. The app must be installed on device. * @param {string?} targetBundleId test target bundle id. - * @param {XctestOption?} opts addition options to specific XCTestConfiguration and app launch env + * @param {Partial} opts addition options to specific XCTestConfiguration and app launch env */ constructor(udid, xctestBundleId, targetBundleId = null, opts = {}) { this.udid = udid; @@ -81,6 +86,7 @@ class Xctest { } const targetName = execName.substr(0, execName.indexOf(XCTEST_EXECUTABLE_SUFFIX)); const xctestPath = `${TMP_FOLDER_PREFIX}/${targetName}-${sessionIdentifier.toUpperCase()}${XCTEST_CONFIGURATION_EXTENSION}`; + // @ts-ignore This works const xctestConfiguration = new XCTestConfiguration({ ...this._conf, // properties below should not be override @@ -189,6 +195,7 @@ class Xctest { this._executing = true; const msg = new DTXMessageAuxBuffer(); msg.appendObject(XCODE_VERSION); + // @ts-ignore _execTestPlanSession must be defined here this._execTestPlanSession._callChannel(false, MAGIC_CHANNEL, '_IDE_startExecutingTestPlanWithProtocolVersion:', msg); }; const showLogMessage = (message) => { @@ -213,14 +220,17 @@ class Xctest { const msg = new DTXMessageAuxBuffer(); msg.appendObject(pid); if (majorVersion >= MAJOR_IOS_VERSION_12) { + // @ts-ignore _initialControlSession must be defined here return await this._initialControlSession.callChannel(DAEMON_CONNECTION_INTERFACE, '_IDE_authorizeTestSessionWithProcessID:', msg); } if (majorVersion <= MAJOR_IOS_VERSION_9) { + // @ts-ignore _initialControlSession must be defined here return await this._initialControlSession.callChannel(DAEMON_CONNECTION_INTERFACE, '_IDE_initiateControlSessionForTestProcessID:', msg); } msg.appendObject(XCODE_VERSION); + // @ts-ignore _initialControlSession must be defined here return await this._initialControlSession.callChannel(DAEMON_CONNECTION_INTERFACE, '_IDE_initiateControlSessionForTestProcessID:protocolVersion:', msg); } diff --git a/package.json b/package.json index 1761fc0..2c52c00 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ ], "dependencies": { "@appium/support": "^4.0.0", - "@babel/runtime": "^7.0.0", "asyncbox": "^2.9.2", "bluebird": "^3.1.1", "bplist-creator": "^0.x", @@ -43,8 +42,9 @@ "uuid": "^9.0.0" }, "scripts": { - "build": "rm -rf build && babel --out-dir=build/lib lib && babel --out-dir=build index.js", + "build": "tsc -b", "dev": "npm run build -- --watch", + "clean": "npm run build -- --clean", "lint": "eslint .", "lint:fix": "npm run lint -- --fix", "precommit-lint": "lint-staged", @@ -67,28 +67,38 @@ "singleQuote": true }, "devDependencies": { - "@appium/eslint-config-appium": "^6.0.0", - "@babel/cli": "^7.18.10", - "@babel/core": "^7.18.10", - "@babel/eslint-parser": "^7.18.9", - "@babel/plugin-transform-runtime": "^7.18.10", - "@babel/preset-env": "^7.18.10", - "@babel/register": "^7.18.9", + "@appium/eslint-config-appium": "^8.0.4", + "@appium/eslint-config-appium-ts": "^0.3.1", + "@appium/tsconfig": "^0.3.0", + "@appium/types": "^0.13.2", "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", - "babel-plugin-source-map-support": "^2.2.0", + "@types/bluebird": "^3.5.38", + "@types/chai": "^4.3.5", + "@types/chai-as-promised": "^7.1.5", + "@types/lodash": "^4.14.196", + "@types/mocha": "^10.0.1", + "@types/node": "^20.4.7", + "@types/sinon": "^10.0.16", + "@types/sinon-chai": "^3.2.9", + "@types/teen_process": "2.0.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "conventional-changelog-conventionalcommits": "^6.0.0", - "eslint": "^7.32.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-mocha": "^9.0.0", - "eslint-plugin-promise": "^6.0.0", + "eslint": "^8.46.0", + "eslint-config-prettier": "^8.9.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.28.0", + "eslint-plugin-mocha": "^10.1.0", + "eslint-plugin-promise": "^6.1.1", "lint-staged": "^14.0.0", "mocha": "^10.0.0", "pre-commit": "^1.1.3", "prettier": "^3.0.0", - "semantic-release": "^20.0.2" + "semantic-release": "^20.0.2", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..33d83ef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@appium/tsconfig/tsconfig.json", + "compilerOptions": { + "strict": false, // TODO: make this flag true + "outDir": "build", + "types": ["node"], + "checkJs": true + }, + "include": [ + "index.js", + "lib" + ] +}