18d307f52SEvan Baconimport fs from 'fs'; 28d307f52SEvan Baconimport path from 'path'; 38d307f52SEvan Baconimport { Stream } from 'stream'; 48d307f52SEvan Baconimport temporary from 'tempy'; 58d307f52SEvan Baconimport { promisify } from 'util'; 68d307f52SEvan Bacon 78d307f52SEvan Baconimport { ensureDirectoryAsync } from './dir'; 88d307f52SEvan Baconimport { CommandError } from './errors'; 98d307f52SEvan Baconimport { extractAsync } from './tar'; 10*8a424bebSJames Ideimport { createCachedFetch, fetchAsync } from '../api/rest/client'; 11*8a424bebSJames Ideimport { FetchLike, ProgressCallback } from '../api/rest/client.types'; 128d307f52SEvan Bacon 13474a7a4bSEvan Baconconst debug = require('debug')('expo:utils:downloadAppAsync') as typeof console.log; 14474a7a4bSEvan Bacon 158d307f52SEvan Baconconst TIMER_DURATION = 30000; 168d307f52SEvan Bacon 178d307f52SEvan Baconconst pipeline = promisify(Stream.pipeline); 188d307f52SEvan Bacon 198d307f52SEvan Baconasync function downloadAsync({ 208d307f52SEvan Bacon url, 218d307f52SEvan Bacon outputPath, 228d307f52SEvan Bacon cacheDirectory, 238d307f52SEvan Bacon onProgress, 248d307f52SEvan Bacon}: { 258d307f52SEvan Bacon url: string; 268d307f52SEvan Bacon outputPath: string; 278d307f52SEvan Bacon cacheDirectory?: string; 288d307f52SEvan Bacon onProgress?: ProgressCallback; 298d307f52SEvan Bacon}) { 308c8eefe0SEvan Bacon let fetchInstance: FetchLike = fetchAsync; 318d307f52SEvan Bacon if (cacheDirectory) { 328d307f52SEvan Bacon // Reconstruct the cached fetch since caching could be disabled. 338d307f52SEvan Bacon fetchInstance = createCachedFetch({ 348d307f52SEvan Bacon // We'll use a 1 week cache for versions so older values get flushed out eventually. 358d307f52SEvan Bacon ttl: 1000 * 60 * 60 * 24 * 7, 368d307f52SEvan Bacon // Users can also nuke their `~/.expo` directory to clear the cache. 378d307f52SEvan Bacon cacheDirectory, 388d307f52SEvan Bacon }); 398d307f52SEvan Bacon } 408d307f52SEvan Bacon 41474a7a4bSEvan Bacon debug(`Downloading ${url} to ${outputPath}`); 4282f3de79SEvan Bacon const res = await fetchInstance(url, { 438d307f52SEvan Bacon timeout: TIMER_DURATION, 448d307f52SEvan Bacon onProgress, 458d307f52SEvan Bacon }); 468d307f52SEvan Bacon if (!res.ok) { 478d307f52SEvan Bacon throw new CommandError( 488d307f52SEvan Bacon 'FILE_DOWNLOAD', 498d307f52SEvan Bacon `Unexpected response: ${res.statusText}. From url: ${url}` 508d307f52SEvan Bacon ); 518d307f52SEvan Bacon } 528d307f52SEvan Bacon return pipeline(res.body, fs.createWriteStream(outputPath)); 538d307f52SEvan Bacon} 548d307f52SEvan Bacon 558d307f52SEvan Baconexport async function downloadAppAsync({ 568d307f52SEvan Bacon url, 578d307f52SEvan Bacon outputPath, 588d307f52SEvan Bacon extract = false, 598d307f52SEvan Bacon cacheDirectory, 608d307f52SEvan Bacon onProgress, 618d307f52SEvan Bacon}: { 628d307f52SEvan Bacon url: string; 638d307f52SEvan Bacon outputPath: string; 648d307f52SEvan Bacon extract?: boolean; 658d307f52SEvan Bacon cacheDirectory?: string; 668d307f52SEvan Bacon onProgress?: ProgressCallback; 678d307f52SEvan Bacon}): Promise<void> { 688d307f52SEvan Bacon if (extract) { 698d307f52SEvan Bacon // For iOS we download the ipa to a file then pass that file into the extractor. 708d307f52SEvan Bacon // In the future we should just pipe the `res.body -> tar.extract` directly. 718d307f52SEvan Bacon // I tried this and it created some weird errors where observing the data stream 728d307f52SEvan Bacon // would corrupt the file causing tar to fail with `TAR_BAD_ARCHIVE`. 738d307f52SEvan Bacon const tmpPath = temporary.file({ name: path.basename(outputPath) }); 748d307f52SEvan Bacon await downloadAsync({ url, outputPath: tmpPath, cacheDirectory, onProgress }); 75474a7a4bSEvan Bacon debug(`Extracting ${tmpPath} to ${outputPath}`); 768a782c0fSEvan Bacon await ensureDirectoryAsync(outputPath); 778d307f52SEvan Bacon await extractAsync(tmpPath, outputPath); 788d307f52SEvan Bacon } else { 798d307f52SEvan Bacon await ensureDirectoryAsync(path.dirname(outputPath)); 808d307f52SEvan Bacon await downloadAsync({ url, outputPath, cacheDirectory, onProgress }); 818d307f52SEvan Bacon } 828d307f52SEvan Bacon} 83