'use strict' const os = require('os') const url = require('url') const path = require('path') const cp = require('child_process') const fs = require('fs-extra') const request = require('request') const extractZip = require('extract-zip') const PHANTOM_VERSION = '2.1.1' const DEFAULT_CDN = 'https://github.com/Medium/phantomjs/releases/download/v2.1.1' const ARM_URL = 'https://github.com/fg2it/phantomjs-on-raspberry/raw/master/rpi-1-2-3/wheezy-jessie/v2.1.1/phantomjs' const PLATFORM = os.platform() const ARCH = os.arch() const getDownloadUrl = () => { let downloadUrl = DEFAULT_CDN + '/phantomjs-' + PHANTOM_VERSION + '-' if (PLATFORM === 'linux' && ARCH === 'x64') { downloadUrl += 'linux-x86_64.tar.bz2' } else if (PLATFORM === 'linux' && ARCH == 'ia32') { downloadUrl += 'linux-i686.tar.bz2' } else if (PLATFORM === 'linux' && ARCH.search('arm') !== -1) { downloadUrl = ARM_URL } else if (PLATFORM === 'darwin') { downloadUrl += 'macosx.zip' } else if (PLATFORM === 'win32') { downloadUrl += 'windows.zip' } else { return null } return downloadUrl } const downloadPhantomjs = (logger, downloadUrl, data_dir) => { return new Promise((resolve, reject) => { const file_name = downloadUrl.split('/').pop() const downloaded_file = path.join(data_dir, file_name) // actually downloads the file const startDownload = (url, dest, cb) => { const file = fs.createWriteStream(dest) // download file request(url) .pipe(file) .on('error', cb) file.on('close', () => { // close file & invoke callback file.close() cb() }) } startDownload(downloadUrl, downloaded_file, err => { if (err) return reject(err) resolve(downloaded_file) }) }) } const extractDownload = (logger, filePath) => { return new Promise((resolve, reject) => { var extractedPath = filePath + '-extract-' + Date.now() var options = {cwd: extractedPath} fs.mkdirsSync(extractedPath, '0777') // Make double sure we have 0777 permissions; some operating systems // default umask does not allow write by default. fs.chmodSync(extractedPath, '0777') if (filePath.substr(-4) === '.zip') { logger.log('PHANTOMJS','Extracting zip contents') extractZip(path.resolve(filePath), {dir: extractedPath}, err => { if (err) { reject(err) } else { resolve(extractedPath) } }) } else { logger.log('PHANTOMJS', 'Extracting tar contents (via spawned process)') cp.execFile('tar', ['jxf', path.resolve(filePath)], options, err => { if (err) { reject(err) } else { resolve(extractedPath) } }) } }) } const copyIntoPlace = (logger, extractedPath, archiveFile, targetPath) => { return new Promise((resolve, reject) => { logger.log('PHANTOMJS', `Removing ${archiveFile}`) fs.removeSync(archiveFile) const folders = fs.readdirSync(extractedPath) const bin_dir = path.join(extractedPath, folders[0], 'bin') const files = fs.readdirSync(path.join(extractedPath, folders[0], 'bin')) let moved = false for (let i = 0; i < files.length; i++) { let file = path.join(bin_dir, files[i]) let filename = files[i] let target = targetPath + path.sep + filename logger.log('PHANTOMJS', `Copying extracted binary ${file} -> ${target}`) fs.moveSync(file, target) logger.log('PHANTOMJS', `Removing temporary download folder ${extractedPath}`) fs.removeSync(extractedPath) moved = target } if (moved !== false) { resolve(moved) } else { reject() } }) } const download = async (logger, phantom_bin, data_dir) => { const download_url = getDownloadUrl() let downloaded_file = '' let extracted_folder = '' let moved_file = '' // error out if we couldn't find a suitable platform/arch combination if (download_url === null) { logger.log('ERROR', `Unexpected platform or architecture: ${PLATFORM} / ${ARCH}\n` + 'It seems there is no binary available for your platform/architecture\n' + `Try to install PhantomJS on your own & copy/link the executable to "${data_dir}"`) return false } logger.log('PHANTOMJS', `Downloading from "${download_url}" to "${data_dir}/"`) // download phantomjs try { downloaded_file = await downloadPhantomjs(logger, download_url, data_dir) logger.log('PHANTOMJS', 'Download finished') } catch (e) { logger.log('ERROR', 'Error downloading PhantomJS - ' + e) return false } // extract phantomjs (if needed) // if it´s ARM, we´re done now if (ARCH.search('arm') !== -1) return true logger.log('PHANTOMJS', `Extracting PhantomJS binary: ${downloaded_file}`) try { extracted_folder = await extractDownload(logger, downloaded_file) } catch (e) { logger.log('ERROR', 'Error extracting PhantomJS - ' + e) return false } logger.log('PHANTOMJS', 'Successfully extracted archive: "${extracted_folder}"') // move Phantom executable logger.log('PHANTOMJS', 'Moving executable to top level') try { moved_file = await copyIntoPlace(logger, extracted_folder, downloaded_file, data_dir) } catch (e) { logger.log('PHANTOMJS', 'Could not find extracted file') return false } // make phantomjs executable try { fs.chmodSync(moved_file, '755') } catch (e) { logger.log('PHANTOMJS', 'Possible error with rights of downloaded Phantom binary') } return true } module.exports = (logger) => { return { download: download.bind(this, logger) } }