Skip to content

Commit

Permalink
chore(functions): use isolated-function
Browse files Browse the repository at this point in the history
It drops vm2 which is considered unsecured in favour of isolated-function
  • Loading branch information
Kikobeats committed Sep 11, 2024
1 parent 7b80314 commit 47e9a9b
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 322 deletions.
7 changes: 2 additions & 5 deletions packages/function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,10 @@
"dependencies": {
"@browserless/errors": "^10.5.2",
"debug-logfmt": "~1.2.1",
"deepmerge": "~4.3.1",
"p-reflect": "~2.1.0",
"isolated-function": "~0.1.4",
"p-retry": "~4.6.1",
"p-timeout": "~4.1.0",
"require-one-of": "~1.0.19",
"serialize-error": "~8.1.0",
"vm2": "~3.9.19"
"require-one-of": "~1.0.19"
},
"devDependencies": {
"@browserless/test": "^10.5.2",
Expand Down
64 changes: 24 additions & 40 deletions packages/function/src/function.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,35 @@
'use strict'

const isolatedFunction = require('isolated-function')
const fs = require('fs/promises')
const path = require('path')

const createVm = require('./vm')

const scriptPath = path.resolve(__dirname, 'function.js')

const createFn = code => `
async ({ url, gotoOpts, browserWSEndpoint, ...opts }) => {
const { serializeError } = require('serialize-error')
const getBrowserless = require('browserless')
const browserless = await getBrowserless({ mode: 'connect', browserWSEndpoint }).createContext()
const fnWrapper = fn => (page, response) => {
if (!response && opts.response) {
const { status, statusText, headers, html } = opts.response
response = {
ok: () => status === 0 || (status >= 200 && opts.status <= 299),
fromCache: () => false,
fromServiceWorker: () => false,
url: () => url,
text: () => html,
statusText: () => statusText,
json: () => JSON.parse(html),
headers: () => headers,
status: () => status
}
}
return fn({ ...opts, page, response, url })
}
const browserFn = browserless.evaluate(fnWrapper(${code}), gotoOpts)
async (url, browserWSEndpoint, opts) => {
const puppeteer = require('@cloudflare/puppeteer')
const browser = await puppeteer.connect({ browserWSEndpoint })
const page = (await browser.pages())[1]
try {
const value = await browserFn(url)
return { isFulfilled: true, isRejected: false, value }
} catch (error) {
return { isFulfilled: false, isRejected: true, reason: serializeError(error) }
return await (${code})({ page, ...opts })
} finally {
await browserless.destroyContext()
await browserless.browser().then(browser => browser.disconnect())
await browser.disconnect()
}
}`

module.exports = ({ url, code, vmOpts, gotoOpts, browserWSEndpoint, ...opts }) => {
const vm = createVm(vmOpts)
const fn = createFn(code)
const run = vm(fn, scriptPath)
return run({ url, gotoOpts, browserWSEndpoint, ...opts })
module.exports = async ({ url, code, vmOpts, browserWSEndpoint, ...opts }) => {
const [fn, teardown] = isolatedFunction(createFn(code), {
...vmOpts,
tmpdir: async () => {
const basepath = path.resolve(__dirname, '../node_modules/.cache')
const tmp = await fs.mkdtemp(path.join(basepath, 'compile-'))
return tmp
},
throwError: false
})

const result = await fn(url, browserWSEndpoint, opts)

await teardown()

return result
}
94 changes: 51 additions & 43 deletions packages/function/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const { ensureError, browserTimeout } = require('@browserless/errors')
const { browserTimeout } = require('@browserless/errors')
const debug = require('debug-logfmt')('browserless:function')
const requireOneOf = require('require-one-of')
const pTimeout = require('p-timeout')
Expand All @@ -12,49 +12,57 @@ const runFunction = require('./function')

const stringify = fn => fn.toString().trim().replace(/;$/, '')

module.exports = (
fn,
{ getBrowserless = requireOneOf(['browserless']), retry = 2, timeout = 30000, ...opts } = {}
) => {
return async (url, fnOpts = {}) => {
const browserlessPromise = getBrowserless()
let isRejected = false

async function run () {
const browserless = await browserlessPromise
const browser = await browserless.browser()
const browserWSEndpoint = browser.wsEndpoint()

const { value, reason, isFulfilled } = await runFunction({
url,
code: stringify(fn),
browserWSEndpoint,
...opts,
...fnOpts
})
module.exports =
(
fn,
{
getBrowserless = requireOneOf(['browserless']),
retry = 2,
timeout = 30000,
gotoOpts,
...opts
} = {}
) =>
async (url, fnOpts = {}) => {
const browserlessPromise = getBrowserless()
let isRejected = false

if (isFulfilled) return value
throw ensureError(reason)
}
async function run () {
const browser = await browserlessPromise
const browserless = await browser.createContext()

const task = () =>
pRetry(run, {
retries: retry,
onFailedAttempt: async error => {
if (error.name === 'AbortError') throw error
if (isRejected) throw new AbortError()
await (await browserlessPromise).respawn()
const { message, attemptNumber, retriesLeft } = error
debug('retry', { attemptNumber, retriesLeft, message })
}
})
const withVM = browserless.withPage((page, goto) => async () => {
const { device } = await goto(page, { url, ...gotoOpts })
return runFunction({
url,
code: stringify(fn),
browserWSEndpoint: (await browserless.browser()).wsEndpoint(),
device,
...opts,
...fnOpts
})
})

return withVM()
}

// main
const result = await pTimeout(task(), timeout, () => {
isRejected = true
throw browserTimeout({ timeout })
})
const task = () =>
pRetry(run, {
retries: retry,
onFailedAttempt: async error => {
if (error.name === 'AbortError') throw error
if (isRejected) throw new AbortError()
await (await browserlessPromise).respawn()
const { message, attemptNumber, retriesLeft } = error
debug('retry', { attemptNumber, retriesLeft, message })
}
})

return result
}
}
// main
const result = await pTimeout(task(), timeout, () => {
isRejected = true
throw browserTimeout({ timeout })
})

return result
}
34 changes: 0 additions & 34 deletions packages/function/src/vm.js

This file was deleted.

Loading

0 comments on commit 47e9a9b

Please sign in to comment.