• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

microlinkhq / browserless / 38434385992

13 Oct 2026 04:54PM UTC coverage: 77.396%. First build
38434385992

Pull #994

github

web-flow
Merge 1748a8fa3 into b8fbd261a
Pull Request #994: fix(function): pass the browser socket as an isolate egress exception

2133 of 2419 branches covered (88.18%)

Branch coverage included in aggregate %.

36 of 38 new or added lines in 1 file covered. (94.74%)

7728 of 10322 relevant lines covered (74.87%)

58228.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.86
/packages/function/src/function.js
1
'use strict'
14 ✔
2

14 ✔
3
const { SLOT } = require('isolated-function')
14 ✔
4
const net = require('node:net')
14 ✔
5

14 ✔
6
const template = require('./template')
14 ✔
7

14 ✔
8
// `new URL()` drops a scheme's default port, so `ws://127.0.0.1:80` has an
14 ✔
9
// empty `url.port`. The port is still in the raw authority. An endpoint that
14 ✔
10
// names no port stays ungranted.
14 ✔
11
const portOf = (endpoint, url) => {
14 ✔
12
  if (url.port !== '') return url.port
40 ✔
13
  const scheme = endpoint.indexOf('://')
9 ✔
14
  if (scheme === -1) return ''
40 !
15
  const authority = endpoint.slice(scheme + 3).split(/[/?#]/, 1)[0]
9 ✔
16
  const colon = authority.lastIndexOf(':')
9 ✔
17
  if (colon === -1 || authority.lastIndexOf(']') > colon) return ''
40 ✔
18
  const port = authority.slice(colon + 1)
3 ✔
19
  return /^\d+$/.test(port) ? port : ''
40 !
20
}
40 ✔
21

14 ✔
22
const egressOf = endpoint => {
14 ✔
23
  if (typeof endpoint !== 'string') return []
72 ✔
24
  let url
40 ✔
25
  try {
40 ✔
26
    url = new URL(endpoint)
40 ✔
27
  } catch {
53 !
NEW
28
    return []
×
NEW
29
  }
×
30
  const port = portOf(endpoint, url)
40 ✔
31
  if (port === '') return []
53 ✔
32
  const host =
34 ✔
33
    url.hostname.startsWith('[') && url.hostname.endsWith(']')
48 ✔
34
      ? url.hostname.slice(1, -1)
72 ✔
35
      : url.hostname
72 ✔
36
  if (net.isIP(host) === 0) return []
72 ✔
37
  return [net.isIP(host) === 6 ? `[${host}]:${port}` : `${host}:${port}`]
72 ✔
38
}
72 ✔
39

14 ✔
40
const [nodeMajor] = process.version.slice(1).split('.').map(Number)
14 ✔
41

14 ✔
42
/**
14 ✔
43
 * The template is built around `SLOT` rather than the user code, so every
14 ✔
44
 * function with the same page shape shares one bundled program and only the
14 ✔
45
 * slot is filled per call, instead of re-bundling the whole program.
14 ✔
46
 */
14 ✔
47
module.exports =
14 ✔
48
  isolatedFunction =>
14 ✔
49
    async ({
23 ✔
50
      url,
72 ✔
51
      code,
72 ✔
52
      vmOpts,
72 ✔
53
      browserWSEndpoint,
72 ✔
54
      extendPage,
72 ✔
55
      hostPage,
72 ✔
56
      needsNetwork = template.needsBrowser(code, { ...extendPage, ...hostPage }),
72 ✔
57
      source = template(SLOT, {
72 ✔
58
        usesPage: template.isUsingPage(code),
72 ✔
59
        needsBrowser: needsNetwork,
72 ✔
60
        extendPage,
72 ✔
61
        hostPage
72 ✔
62
      }),
72 ✔
63
      ...opts
72 ✔
64
    }) => {
72 ✔
65
      const permissions = needsNetwork && nodeMajor >= 25 ? ['net'] : []
72 !
66
      const vmOptsAllow = vmOpts?.allow || {}
72 ✔
67
      // `isolated-function` rejects `slot` unless the sentinel appears once.
72 ✔
68
      // A second copy means an extendPage method mentioned it; inline the user
72 ✔
69
      // code and take a full build so that call still runs.
72 ✔
70
      const copies = source.split(SLOT).length - 1
72 ✔
71
      const program =
72 ✔
72
      copies > 1
72 ✔
73
        ? template(code, {
72 ✔
74
          usesPage: template.isUsingPage(code),
1 ✔
75
          needsBrowser: needsNetwork,
1 ✔
76
          extendPage,
1 ✔
77
          hostPage
1 ✔
78
        })
1 ✔
79
        : source
72 ✔
80
      const host = template.exposedHost(hostPage, extendPage)
72 ✔
81
      const fn = isolatedFunction(program, {
72 ✔
82
        ...vmOpts,
72 ✔
83
        ...(copies === 1 && { slot: code }),
72 ✔
84
        ...(host && { host }),
72 ✔
85
        allow: {
72 ✔
86
          ...vmOptsAllow,
72 ✔
87
          permissions: [...(vmOptsAllow.permissions || []), ...permissions],
72 ✔
88
          egress: [
72 ✔
89
            ...(Array.isArray(vmOptsAllow.egress) ? vmOptsAllow.egress : []),
72 ✔
90
            ...egressOf(browserWSEndpoint)
72 ✔
91
          ]
72 ✔
92
        },
72 ✔
93
        throwError: false
72 ✔
94
      })
72 ✔
95
      return fn(url, browserWSEndpoint, opts)
72 ✔
96
    }
72 ✔
97

14 ✔
98
module.exports.isUsingPage = template.isUsingPage
14 ✔
99
module.exports.needsBrowser = template.needsBrowser
14 ✔
100
module.exports.buildTemplate = template
14 ✔
101
module.exports.inspect = template.inspect
14 ✔
102
module.exports.PAGE_NOT_FOUND = template.PAGE_NOT_FOUND
14 ✔
103
module.exports.RESOLVE_PAGE = template.RESOLVE_PAGE
14 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc