@devicecloud.dev/dcd
Version:
Better cloud maestro testing
139 lines (138 loc) • 4.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetadataExtractorService = exports.IosZipMetadataExtractor = exports.IosAppMetadataExtractor = exports.AndroidMetadataExtractor = void 0;
const AppInfoParser = require("app-info-parser");
const bplist_parser_1 = require("bplist-parser");
const promises_1 = require("node:fs/promises");
const path = require("node:path");
const StreamZip = require("node-stream-zip");
const plist_1 = require("plist");
/**
* Extracts metadata from Android APK files
*/
class AndroidMetadataExtractor {
canHandle(filePath) {
return filePath.endsWith('.apk');
}
async extract(filePath) {
const parser = new AppInfoParser(filePath);
const result = await parser.parse();
return { appId: result.package, platform: 'android' };
}
}
exports.AndroidMetadataExtractor = AndroidMetadataExtractor;
/**
* Extracts metadata from iOS .app directories
*/
class IosAppMetadataExtractor {
canHandle(filePath) {
return filePath.endsWith('.app');
}
async extract(filePath) {
const infoPlistPath = path.normalize(path.join(filePath, 'Info.plist'));
const buffer = await (0, promises_1.readFile)(infoPlistPath);
const data = await this.parseInfoPlist(buffer);
const appId = data.CFBundleIdentifier;
return { appId, platform: 'ios' };
}
async parseInfoPlist(buffer) {
let data;
const bufferType = buffer[0];
if (bufferType === 60 ||
bufferType === '<' ||
bufferType === 239) {
data = (0, plist_1.parse)(buffer.toString());
}
else if (bufferType === 98) {
data = (0, bplist_parser_1.parseBuffer)(buffer)[0];
}
else {
throw new Error('Unknown plist buffer type.');
}
return data;
}
}
exports.IosAppMetadataExtractor = IosAppMetadataExtractor;
/**
* Extracts metadata from iOS .zip files containing .app bundles
*/
class IosZipMetadataExtractor {
canHandle(filePath) {
return filePath.endsWith('.zip');
}
async extract(filePath) {
return new Promise((resolve, reject) => {
const zip = new StreamZip({ file: filePath });
zip.on('ready', () => {
// Get all entries and sort them by path depth
const entries = Object.values(zip.entries());
const sortedEntries = entries.sort((a, b) => {
const aDepth = a.name.split('/').length;
const bDepth = b.name.split('/').length;
return aDepth - bDepth;
});
// Find the first Info.plist in the shallowest directory
const infoPlist = sortedEntries.find((e) => e.name.endsWith('.app/Info.plist'));
if (!infoPlist) {
reject(new Error('Failed to find info plist'));
return;
}
const buffer = zip.entryDataSync(infoPlist.name);
this.parseInfoPlist(buffer)
.then((data) => {
const appId = data.CFBundleIdentifier;
zip.close();
resolve({ appId, platform: 'ios' });
})
.catch(reject);
});
zip.on('error', reject);
});
}
async parseInfoPlist(buffer) {
let data;
const bufferType = buffer[0];
if (bufferType === 60 ||
bufferType === '<' ||
bufferType === 239) {
data = (0, plist_1.parse)(buffer.toString());
}
else if (bufferType === 98) {
data = (0, bplist_parser_1.parseBuffer)(buffer)[0];
}
else {
throw new Error('Unknown plist buffer type.');
}
return data;
}
}
exports.IosZipMetadataExtractor = IosZipMetadataExtractor;
/**
* Service for extracting app metadata from various file formats
*/
class MetadataExtractorService {
extractors = [
new AndroidMetadataExtractor(),
new IosZipMetadataExtractor(),
new IosAppMetadataExtractor(),
];
/**
* Extract app metadata from a file
* @param filePath - Path to the app file (.apk, .app, or .zip)
* @returns App metadata or undefined if extraction fails
*/
async extract(filePath) {
const extractor = this.extractors.find((e) => e.canHandle(filePath));
if (!extractor) {
return undefined;
}
try {
return await extractor.extract(filePath);
}
catch {
console.warn('Failed to extract app metadata, please share with support@devicecloud.dev so we can improve our parsing.');
return undefined;
}
}
}
exports.MetadataExtractorService = MetadataExtractorService;