isolated-function 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,8 +6,9 @@
6
6
 
7
7
  **Highlights**
8
8
 
9
- - Based on [v8-sandbox](https://github.com/fulcrumapp/v8-sandbox).
9
+ - Based in [Node.js Permission Model](https://nodejs.org/api/permissions.html#permission-model)
10
10
  - Auto install npm dependencies.
11
+ - Memory limit support.
11
12
  - Timeout support.
12
13
 
13
14
  ## Install
@@ -19,10 +20,10 @@ npm install isolated-function --save
19
20
  ## Usage
20
21
 
21
22
  ```js
22
- const isoaltedFunction = require('isolated-function')
23
+ const isolatedFunction = require('isolated-function')
23
24
 
24
25
  /* This function will run in a sandbox, in a separate process */
25
- const sum = isoaltedFunction((y, z) => y + z)
26
+ const sum = isolatedFunction((y, z) => y + z)
26
27
 
27
28
  /* Interact with it as usual from your main code */
28
29
  const result = await sum(3, 2)
@@ -33,7 +34,7 @@ console.log(result)
33
34
  You can also use `require' for external dependencies:
34
35
 
35
36
  ```js
36
- const isEmoji = isoaltedFunction(emoji => {
37
+ const isEmoji = isolatedFunction(emoji => {
37
38
  const isEmoji = require('is-standard-emoji')
38
39
  return isEmoji(emoji)
39
40
  })
@@ -48,7 +49,7 @@ It's intentionally not possible to expose any Node.js objects or functions direc
48
49
 
49
50
  ## API
50
51
 
51
- ### isoaltedFunction(snippet, [options])
52
+ ### isolatedFunction(snippet, [options])
52
53
 
53
54
  #### snippet
54
55
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "isolated-function",
3
3
  "description": "Runs untrusted code in a Node.js v8 sandbox.",
4
4
  "homepage": "https://github.com/Kikobeats/isolated-function",
5
- "version": "0.0.4",
5
+ "version": "0.0.5",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
8
  ".": "./src/index.js"
@@ -33,6 +33,7 @@
33
33
  "v8"
34
34
  ],
35
35
  "dependencies": {
36
+ "@kikobeats/time-span": "~1.0.5",
36
37
  "acorn": "~8.12.1",
37
38
  "acorn-walk": "~8.3.3",
38
39
  "ensure-error": "~3.0.1",
package/src/compile.js CHANGED
@@ -11,6 +11,8 @@ const path = require('path')
11
11
 
12
12
  const generateTemplate = require('./template')
13
13
 
14
+ const MINIFY = process.env.ISOLATED_FUNCTIONS_MINIFY !== 'false'
15
+
14
16
  const packageManager = (() => {
15
17
  try {
16
18
  execSync('which pnpm').toString().trim()
@@ -65,12 +67,10 @@ module.exports = async snippet => {
65
67
  cwd: tmp.cwd
66
68
  })
67
69
 
68
- // return tmp
69
-
70
70
  const result = await esbuild.build({
71
71
  entryPoints: [tmp.filepath],
72
72
  bundle: true,
73
- // minify: true,
73
+ minify: MINIFY,
74
74
  write: false,
75
75
  platform: 'node'
76
76
  })
package/src/index.js CHANGED
@@ -1,33 +1,73 @@
1
1
  'use strict'
2
2
 
3
3
  const { deserializeError } = require('serialize-error')
4
+ const timeSpan = require('@kikobeats/time-span')()
4
5
  const $ = require('tinyspawn')
6
+ const path = require('path')
5
7
 
6
8
  const compile = require('./compile')
7
9
 
8
- class TimeoutError extends Error {
9
- constructor (message) {
10
- super(message)
11
- this.name = 'TimeoutError'
12
- }
10
+ const createError = ({ name, message, ...props }) => {
11
+ const error = new Error(message)
12
+ error.name = name
13
+ Object.assign(error, props)
14
+ return error
15
+ }
16
+
17
+ const flags = ({ filename, memory }) => {
18
+ const flags = ['--experimental-permission', `--allow-fs-read=${filename}`]
19
+ if (memory) flags.push(`--max-old-space-size=${memory}`)
20
+ return flags.join(' ')
13
21
  }
14
22
 
15
- module.exports = (snippet, { timeout = 0 } = {}) => {
23
+ module.exports = (snippet, { timeout = 0, memory } = {}) => {
16
24
  if (typeof snippet !== 'function') throw new TypeError('Expected a function')
17
25
  const compilePromise = compile(snippet)
18
26
 
19
27
  const fn = async (...args) => {
28
+ let duration
20
29
  try {
21
30
  const { filepath } = await compilePromise
22
- const { stdout } = await $(`node ${filepath} ${JSON.stringify(args)}`, {
31
+
32
+ const cwd = path.dirname(filepath)
33
+ const filename = path.basename(filepath)
34
+ const cmd = `node ${flags({ filename, memory })} ${filename} ${JSON.stringify(args)}`
35
+
36
+ duration = timeSpan()
37
+ const { stdout } = await $(cmd, {
38
+ cwd,
23
39
  timeout,
24
40
  killSignal: 'SIGKILL'
25
41
  })
26
- const { isFulfilled, value } = JSON.parse(stdout)
27
- if (isFulfilled) return value
42
+ const { isFulfilled, value, profiling } = JSON.parse(stdout)
43
+ profiling.duration = duration()
44
+ if (isFulfilled) return [value, profiling]
28
45
  throw deserializeError(value)
29
46
  } catch (error) {
30
- if (error.killed) throw new TimeoutError('Execution timed out')
47
+ if (error.signalCode === 'SIGTRAP') {
48
+ throw createError({
49
+ name: 'MemoryError',
50
+ message: 'Out of memory',
51
+ profiling: { duration: duration() }
52
+ })
53
+ }
54
+
55
+ if (error.signalCode === 'SIGKILL') {
56
+ throw createError({
57
+ name: 'TimeoutError',
58
+ message: 'Execution timed out',
59
+ profiling: { duration: duration() }
60
+ })
61
+ }
62
+
63
+ if (error.code === 'ERR_ACCESS_DENIED') {
64
+ throw createError({
65
+ name: 'PermissionError',
66
+ message: `Access to '${error.permission}' has been restricted`,
67
+ profiling: { duration: duration() }
68
+ })
69
+ }
70
+
31
71
  throw error
32
72
  }
33
73
  }
@@ -2,22 +2,26 @@
2
2
 
3
3
  const SERIALIZE_ERROR = require('./serialize-error')
4
4
 
5
- const generateTemplate = snippet => {
6
- const template = `
5
+ const generateTemplate = snippet => `
7
6
  const args = JSON.parse(process.argv[2])
8
7
 
9
8
  ;(async () => {
9
+ let value
10
+ let isFulfilled
11
+
10
12
  try {
11
- const value = await (${snippet.toString()})(...args)
12
- console.log(JSON.stringify({ isFulfilled: true, value }))
13
+ value = await (${snippet.toString()})(...args)
14
+ isFulfilled = true
13
15
  } catch (error) {
14
- console.log(JSON.stringify({ isFulfilled: false, value: ${SERIALIZE_ERROR}(error) }))
16
+ value = ${SERIALIZE_ERROR}(error)
17
+ isFulfilled = false
18
+ } finally {
19
+ console.log(JSON.stringify({
20
+ isFulfilled,
21
+ value,
22
+ profiling: { memory: process.memoryUsage().rss }
23
+ }))
15
24
  }
16
- })()
17
-
18
- `
19
-
20
- return template
21
- }
25
+ })()`
22
26
 
23
27
  module.exports = generateTemplate