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:
committed by
Facebook Github Bot
parent
8d93946739
commit
40f50d48e3
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user