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

microlinkhq / browserless / 38438791069

13 Oct 2026 05:27PM UTC coverage: 77.371%. First build
38438791069

Pull #994

github

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

2136 of 2419 branches covered (88.3%)

Branch coverage included in aggregate %.

45 of 47 new or added lines in 1 file covered. (95.74%)

7728 of 10330 relevant lines covered (74.81%)

58183.6 hits per line

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

96.82
/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
43 ✔
13
  const scheme = endpoint.indexOf('://')
11 ✔
14
  if (scheme === -1) return ''
16 !
15
  const authority = endpoint.slice(scheme + 3).split(/[/?#]/, 1)[0]
11 ✔
16
  const colon = authority.lastIndexOf(':')
11 ✔
17
  if (colon === -1) return ''
16 ✔
18
  const port = authority.slice(colon + 1)
5 ✔
19
  // `080` is port 80. The dial uses the number, so the grant has to as well.
5 ✔
20
  return /^\d+$/.test(port) ? String(Number(port)) : ''
43 ✔
21
}
43 ✔
22

14 ✔
23
const pair = (address, port) => {
14 ✔
24
  const value = String(port)
20 ✔
25
  return net.isIP(address) === 6 ? `[${address}]:${value}` : `${address}:${value}`
20 ✔
26
}
20 ✔
27

14 ✔
28
const egressOf = endpoint => {
14 ✔
29
  if (typeof endpoint !== 'string') return []
75 ✔
30
  let url
43 ✔
31
  try {
43 ✔
32
    url = new URL(endpoint)
43 ✔
33
  } catch {
56 !
NEW
34
    return []
×
NEW
35
  }
×
36
  const port = portOf(endpoint, url)
43 ✔
37
  if (port === '') return []
56 ✔
38
  const host =
36 ✔
39
    url.hostname.startsWith('[') && url.hostname.endsWith(']')
51 ✔
40
      ? url.hostname.slice(1, -1)
75 ✔
41
      : url.hostname
75 ✔
42
  if (net.isIP(host) === 0) return []
75 ✔
43
  return [net.isIP(host) === 6 ? `[${host}]:${port}` : `${host}:${port}`]
75 ✔
44
}
75 ✔
45

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

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

14 ✔
106
module.exports.isUsingPage = template.isUsingPage
14 ✔
107
module.exports.needsBrowser = template.needsBrowser
14 ✔
108
module.exports.buildTemplate = template
14 ✔
109
module.exports.inspect = template.inspect
14 ✔
110
module.exports.PAGE_NOT_FOUND = template.PAGE_NOT_FOUND
14 ✔
111
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