Created
October 27, 2016 08:36
-
-
Save anonymous/a1f2d6f56db7d8bbf0f8616ab5018346 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import phantom from 'phantom' | |
import { PassThrough } from 'stream' | |
import createError from 'http-errors' | |
import reemit from 're-emitter' | |
/* eslint-disable max-len */ | |
const mobileUA = 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7' | |
const desktopUA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36' | |
const getShotSize = (width, height) => ({ | |
...(Array.isArray(width) && width.length > 1 && { width: width[1] }), | |
...(Array.isArray(height) && height.length > 1 && { height: height[1] }), | |
}) | |
const getScreenSize = (width, height) => ({ | |
width, | |
height, | |
...(Array.isArray(width) && { width: width[0] }), | |
...(Array.isArray(height) && { height: height[0] }), | |
}) | |
// returns a phantom "singleton" instance. | |
// we re-use the same phantom process for all screenshots | |
// await instance.exit() | |
const initGetPhantom = () => { | |
let instance | |
return async () => { | |
if (instance) return instance | |
instance = await phantom.create() | |
return instance | |
} | |
} | |
// TODO: the getPhantom function should be passed as an | |
// argument, not created here. | |
// TODO: getPhantom() could return a new phantom instance every | |
// X requests, to avoid resource leaks, etc. | |
const getPhantom = initGetPhantom() | |
const takeScreenshot = async (instance, url, { | |
screenSize, | |
shotSize, | |
} = {}, { | |
userAgent, | |
} = {}) => { | |
const page = await instance.createPage() | |
// close asynchronously | |
const close = () => { | |
// TODO: should receive a logger as argument | |
page.close().catch(err => { | |
/* eslint-disable no-console */ | |
console.error(err) | |
}) | |
} | |
try { | |
// TODO: set user-agent in settings.headees? | |
const status = await page.open(url, { | |
operation: 'GET', | |
headers: { | |
'User-Agent': userAgent, | |
}, | |
}) | |
if (status !== 'success') { | |
// TODO: get status code | |
throw createError(400, 'Error opening URL', { | |
detail: { status }, | |
}) | |
} | |
// TODO: could do that in parallel with page.open? | |
await page.property('viewportSize', screenSize) | |
await page.property('clipRect', shotSize) | |
// page.propert('paperSize', shotSize) | |
// wait for onLoadFinished event | |
// TODO: is this *always* fired? | |
/* | |
console.log('waiting onloadfinished') | |
// TODO: add a timeout? | |
await new Promise(resolve => page.on('loadFinished', () => { | |
console.log('onloadfinished') | |
resolve() | |
})) | |
*/ | |
/* | |
IDEA: create a unix named pipe and write there. return a stream | |
that reads from the pipe. delete the pipe when done | |
await page.render('google_home.jpeg', { | |
// TODO: configurable? | |
format: 'jpeg', | |
quality: '100', | |
}) | |
*/ | |
// TODO: configurable format/quality? | |
// const buf = await page.renderBuffer('png', -1) | |
const buf = Buffer.from(await page.renderBase64('PNG'), 'base64') | |
const through = new PassThrough() | |
through.end(buf) | |
close() | |
return through | |
} catch (err) { | |
close() // ensure page is closed | |
throw err | |
} | |
} | |
// If width is an array, first ele used for screen width and second is used for shot width | |
// Idem for height | |
export default ({ | |
width = 1024, // width of browser window | |
height = 768, // height of browser window | |
agent = 'desktop', | |
crop = false, | |
selector, // optional css selector. todo: decode base64? | |
error = false, | |
}, { source: { url } }) => { | |
// TODO: offload this to worker on other machine using task queue? | |
const screenSize = getScreenSize(width, height) | |
const shotSize = getShotSize(width, height) | |
const userAgent = agent === 'mobile' | |
? mobileUA | |
: desktopUA | |
const through = new PassThrough() | |
getPhantom() | |
.then(instance => ( | |
takeScreenshot(instance, url, { screenSize, shotSize }, { userAgent }) | |
)) | |
.then(rs => { | |
reemit(rs, through, ['error']) | |
rs.pipe(through) | |
}) | |
.catch(err => { | |
through.emit('error', err) | |
}) | |
return through | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment