Add physical iOS support to internal build

Summary: Adds support for physical iOS devices, when the necessary dependencies are present. Unfortunately these aren't open sourced yet so the open source build won't get this feature yet.

Reviewed By: priteshrnandgaonkar

Differential Revision: D13001473

fbshipit-source-id: d8c2bcd53b7972bec676717c8af0112800b918d0
This commit is contained in:
John Knox
2018-12-03 11:32:38 -08:00
committed by Facebook Github Bot
parent 8d93946739
commit 40f50d48e3
22 changed files with 603 additions and 89 deletions

View File

@@ -8,6 +8,8 @@
import LogManager from '../fb-stubs/Logger';
import {RecurringError} from './errors';
import {promisify} from 'util';
import child_process from 'child_process';
const exec = promisify(child_process.exec);
const fs = require('fs');
const adb = require('adbkit-fb');
import {
@@ -15,7 +17,10 @@ import {
isInstalled as opensslInstalled,
} from './openssl-wrapper-with-promises';
const path = require('path');
const tmpFile = promisify(require('tmp').file);
const tmp = require('tmp');
const tmpFile = promisify(tmp.file);
const tmpDir = promisify(tmp.dir);
import iosUtil from '../fb-stubs/iOSContainerUtility';
// Desktop file paths
const os = require('os');
@@ -121,15 +126,7 @@ export default class CertificateProvider {
if (os === 'Android') {
return this.getTargetAndroidDeviceId(appName, appDirectory, csr);
} else if (os === 'iOS') {
const matches = /\/Devices\/([^/]+)\//.exec(appDirectory);
if (matches === null || matches.length < 2) {
return Promise.reject(
new Error(
`iOS simulator directory doesn't match expected format: ${appDirectory}`,
),
);
}
return Promise.resolve(matches[1]);
return this.getTargetiOSDeviceId(appName, appDirectory, csr);
}
return Promise.resolve('unknown');
}
@@ -169,6 +166,14 @@ export default class CertificateProvider {
});
}
getRelativePathInAppContainer(absolutePath: string) {
const matches = /Application\/[^/]+\/(.*)/.exec(absolutePath);
if (matches && matches.length === 2) {
return matches[1];
}
throw new Error("Path didn't match expected pattern: " + absolutePath);
}
deployFileToMobileApp(
destination: string,
filename: string,
@@ -176,8 +181,9 @@ export default class CertificateProvider {
csr: string,
os: string,
): Promise<void> {
const appNamePromise = this.extractAppNameFromCSR(csr);
if (os === 'Android') {
const appNamePromise = this.extractAppNameFromCSR(csr);
const deviceIdPromise = appNamePromise.then(app =>
this.getTargetAndroidDeviceId(app, destination, csr),
);
@@ -192,22 +198,54 @@ export default class CertificateProvider {
);
}
if (os === 'iOS' || os === 'windows') {
return new Promise((resolve, reject) => {
fs.writeFile(destination + filename, contents, err => {
if (err) {
reject(
`Invalid appDirectory recieved from ${os} device: ${destination}: ` +
err.toString(),
return promisify(fs.writeFile)(destination + filename, contents).catch(
err => {
if (os === 'iOS') {
// Writing directly to FS failed. It's probably a physical device.
const relativePathInsideApp = this.getRelativePathInAppContainer(
destination,
);
} else {
resolve();
return appNamePromise
.then(appName =>
this.getTargetiOSDeviceId(appName, destination, csr),
)
.then(udid => {
return appNamePromise.then(appName =>
this.pushFileToiOSDevice(
udid,
appName,
relativePathInsideApp,
filename,
contents,
),
);
});
}
});
});
throw new Error(
`Invalid appDirectory recieved from ${os} device: ${destination}: ` +
err.toString(),
);
},
);
}
return Promise.reject(new RecurringError(`Unsupported device os: ${os}`));
}
pushFileToiOSDevice(
udid: string,
bundleId: string,
destination: string,
filename: string,
contents: string,
): Promise<void> {
return tmpDir({unsafeCleanup: true}).then(dir => {
const filePath = path.resolve(dir, filename);
promisify(fs.writeFile)(filePath, contents).then(() =>
iosUtil.push(udid, filePath, bundleId, destination),
);
});
}
getTargetAndroidDeviceId(
appName: string,
deviceCsrFilePath: string,
@@ -215,10 +253,6 @@ export default class CertificateProvider {
): Promise<string> {
return this.adb.listDevices().then((devices: Array<{id: string}>) => {
const deviceMatchList = devices.map(device =>
// To find out which device requested the cert, search them
// all for a matching csr file.
// It's not important to keep these secret from other apps.
// Just need to make sure each app can find its own one.
this.androidDeviceHasMatchingCSR(
deviceCsrFilePath,
device.id,
@@ -248,6 +282,39 @@ export default class CertificateProvider {
});
}
getTargetiOSDeviceId(
appName: string,
deviceCsrFilePath: string,
csr: string,
): Promise<string> {
const matches = /\/Devices\/([^/]+)\//.exec(deviceCsrFilePath);
if (matches && matches.length == 2) {
// It's a simulator, the deviceId is in the filepath.
return Promise.resolve(matches[1]);
}
return iosUtil.targets().then(targets => {
const deviceMatchList = targets.map(target =>
this.iOSDeviceHasMatchingCSR(
deviceCsrFilePath,
target.udid,
appName,
csr,
).then(isMatch => {
return {id: target.udid, isMatch};
}),
);
return Promise.all(deviceMatchList).then(devices => {
const matchingIds = devices.filter(m => m.isMatch).map(m => m.id);
if (matchingIds.length == 0) {
throw new RecurringError(
`No matching device found for app: ${appName}`,
);
}
return matchingIds[0];
});
});
}
androidDeviceHasMatchingCSR(
directory: string,
deviceId: string,
@@ -273,6 +340,42 @@ export default class CertificateProvider {
});
}
iOSDeviceHasMatchingCSR(
directory: string,
deviceId: string,
bundleId: string,
csr: string,
): Promise<boolean> {
const originalFile = this.getRelativePathInAppContainer(
path.resolve(directory, csrFileName),
);
return tmpDir({unsafeCleanup: true})
.then(dir => {
return iosUtil
.pull(deviceId, originalFile, bundleId, dir)
.then(() => dir);
})
.then(dir => {
return promisify(fs.readdir)(dir)
.then(items => {
if (items.length !== 1) {
throw new Error('Conflict in temp dir');
}
return items[0];
})
.then(fileName => {
const copiedFile = path.resolve(dir, fileName);
return promisify(fs.readFile)(copiedFile).then(data =>
data
.toString()
.replace(/\r/g, '')
.trim(),
);
});
})
.then(csrFromDevice => csrFromDevice === csr.replace(/\r/g, '').trim());
}
pushFileToAndroidDevice(
deviceId: string,
app: string,